---
# System prepended metadata

title: Week 14：動態規劃二

---

# Week 14：動態規劃二

:::info
時間：06/11 9:00 ~ 17:00
地點：成大資工系新館 **65304** 教室
主題：動態規劃二
直播連結：https://youtube.com/live/ROYVq_fj7TU?feature=share
:::


# 必作題題解

[TOC]

## 三章_第五節

### [Kattis rijeci](https://open.kattis.com/problems/rijeci)

撰寫者：Eason

簡單觀察可以發現就是費氏數列

:::spoiler code
```cpp=
#include<bits/stdc++.h>
#define ll long long
#define weakson ios::sync_with_stdio(0), cin.tie(0);
using namespace std;

int main(){
    weakson;

    const int M = 45;

    ll dp[46];
    dp[0] = 0;
    dp[1] = 1;

    for (int i = 2; i <= M; i++){
        dp[i] = dp[i - 1] + dp[i - 2];
    }

    int n;
    cin >> n;

    cout << dp[n - 1] << ' ' << dp[n] << '\n';

    return 0;
}
```
:::

---

### [A - Frog 1](https://atcoder.jp/contests/dp/tasks/dp_a)

撰寫者：小麥

當青蛙在第一個石頭的時候因為沒有任何的跳躍，所以cost是零，之後第二顆可以先算好，因為要到第二顆一定只能從第一顆跳。
第三顆就可以考慮兩個選項，從第一顆or從第二顆，這裡的最二顆要是dp的第二顆，dp的第二顆在之前的計算就把從第一顆的cost給算進去了。
之後以此類推，四考慮二、三，五考慮三、四。

:::spoiler code
```cpp=
#include <bits/stdc++.h>

using namespace std;

int n;
vector<int> arr;
vector<int> dp;

signed main() 
{
    // ios::sync_with_stdio(0);
    // cin.tie(0);
    // cout.tie(0);

    cin >> n;
    arr.resize(n);
    for (int i = 0; i < n; i++) 
    {
        cin >> arr[i];
    }

    dp.resize(n + 1);
    dp[1] = 0;
    dp[2] = dp[1] + abs(arr[1] - arr[0]);
    for (int i = 3; i <= n; i++) 
    {
        dp[i] = min(abs(arr[i-1] - arr[i-2]) + dp[i-1], abs(arr[i-1] - arr[i-3]) + dp[i-2]);
    }

    cout << dp[n];

    return 0;
}
```
:::

---

### [Kattis theplank](https://open.kattis.com/problems/theplank)

撰寫者：Eason

題目有說現在只有長度為1、2、3的木頭
所以要接出長度為 n 的木頭
就要從$n-1$、$n-2$、$n-3$的情況取得
可以想成：
>從長度為$n-1$的木頭再接一個長度為 1 的木頭
>從長度為$n-2$的木頭再接一個長度為 2 的木頭
>從長度為$n-3$的木頭再接一個長度為 3 的木頭

因為三種情況最後都只有接一塊木頭
<font color=red>
所以
    從長度為 $n-k$ 的木頭再接一個長度為 $k$ 的木頭之情況總和
一定等於 $n-k$ 的情況總和
</font>

因此可以列出轉移式

$dp_i=dp_{i-1}+dp_{i-2}+dp_{i-3}$

:::spoiler code
$$
\begin{cases}
dp_i=0 & i=0 \\
dp_i=1 & i=1 \\
dp_i=2 & i=2 \\
dp_i=4 & i=3 \\
dp_i=dp_{i-1}+dp_{i-2}+dp_{i-3} & i>3
\end{cases}
$$
```cpp=
#include<bits/stdc++.h>
#define ll long long
#define weakson ios::sync_with_stdio(0), cin.tie(0);
using namespace std;

int main(){
    weakson;
    
    const int N = 24;

    ll dp[25];
    dp[0] = 0;
    dp[1] = 1;
    dp[2] = 2;
    dp[3] = 4;

    for (int i = 4; i <= N; i++){
        dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3];
    }

    int n;
    cin >> n;

    cout << dp[n] << '\n';

    return 0;
}
```
:::

---

### [Dice Combinations](https://cses.fi/problemset/task/1633/)

撰寫者：小麥


:::spoiler code (迭代)
```cpp
#include <bits/stdc++.h>

using namespace std;

const int MOD = 1e9 + 7;
int n;
vector<int> dp;

signed main() 
{
    // ios::sync_with_stdio(0);
    // cin.tie(0);
    // cout.tie(0);

    cin >> n;
    dp.resize(n + 1);
    for (int i = 1; i <= min(n, 6); i++)
    {
        dp[i] = 1;
    }
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= 6; j++)
        {
            if (i <= j)
            {
                break;
            }
            dp[i] += dp[i - j] % MOD;
            dp[i] %= MOD;
        }
    }

    cout << dp[n] << '\n';

    return 0;
}
```
:::

:::spoiler code (遞迴)
```cpp=
#include <bits/stdc++.h>

using namespace std;

const int MOD = 1e9 + 7;
int n;
vector<int> dp;

int getCombination(int n) 
{
    if (dp[n] != 0)
    {
        return dp[n];
    }

    for (int i = 1; i <= 6; i++)
    {
        if (i > n)
        {
            continue;
        }
        dp[n] += getCombination(n - i);
        dp[n] %= MOD;
    }

    return dp[n];
}

signed main() 
{
    // ios::sync_with_stdio(0);
    // cin.tie(0);
    // cout.tie(0);

    cin >> n;
    dp.resize(n + 1);
    dp[0] = 1;
    dp[1] = 1;
    cout << getCombination(n) << "\n";

    return 0;
} 
```
:::

---

### [CSES 1635](https://cses.fi/problemset/task/1635/)

撰寫者：Eason

題目給了 $n$ 種面額
所以要湊出金額 $m$
可以從 $m-a_{i} (i=0,1,2,3,...,n)$ 取得
其實跟 [kattis theplank](https://hackmd.io/W_tftCm1SpahIMf8L_xFAQ#Kattis-theplank) 很像
只是那題只有 3 個木頭
而這題有 $n$ 個面額

:::spoiler code
```cpp=
#include<bits/stdc++.h>
#define weakson ios::sync_with_stdio(0),cin.tie(0);
#define ll long long
using namespace std;

const int MAX = 1e9 + 7;

int main(){
    weakson;

    int n, m;
    cin >> n >> m;

    int a[105];
    ll dp[1000005] = {};

    for(int i = 0; i < n; i++){
        cin >> a[i];
    }

    dp[0] = 1;

    for (int i = 1; i <= m; i++){
        for (int j = 0; j < n; j++){
            if (i - a[j] >= 0){
                dp[i] += dp[i - a[j]] % MAX;
                dp[i] %= MAX;
            }
        }
    }

    cout << dp[m] << '\n';

    return 0;
}

```
:::

---

### [CSES 1638](https://cses.fi/problemset/task/1638/)

撰寫者：小麥 

如果一個格子是可以被走的（非地雷），那麼可以到這一格的步數就是從上面走或從左邊走，因此加起來就行了。
如果一個格子是不可以被走的（地雷)，那這一格就是零，因為這一格無論如何都走不到。

:::spoiler code
```cpp=
#include <bits/stdc++.h>

using namespace std;

const int mod = 1e9 + 7;
int n;
vector<vector<int>> arr;
vector<vector<int>> dp;

signed main() 
{
    // ios::sync_with_stdio(0);
    // cin.tie(0);
    // cout.tie(0);

    cin >> n;
    arr.resize(n, vector<int>(n, 0));  
    dp.resize(n + 1, vector<int>(n + 1, 0));

    for (int i = 0; i < n; i++)
    {
        string row;
        cin >> row;
        for (int j = 0; j < n; j++)
        {
            if (row[j] == '*')
            {
                arr[i][j] = 1;
            }
        }
    }

    bool flag = true, flag2 = true;
    for (int i = 1; i <= n; i++)
    {
        if (arr[i-1][0] != 1 && flag)
        {
            dp[i][1] = 1;
        }
        else
        {
            flag = false;
        }
        if (arr[0][i-1] != 1 && flag2)
        {
            dp[1][i] = 1;
        }
        else
        {
            flag2 = false;
        }
    }

    for (int i = 2; i <= n; i++)
    {
        for (int j = 2; j <= n; j++)
        {
            dp[i][j] = dp[i-1][j]  + dp[i][j-1];
            dp[i][j] %= mod;
            if (arr[i-1][j-1] == 1)
            {
                dp[i][j] = 0;
                continue;
            }
        }
    }

    cout << (dp[n][n]) << "\n";

    return 0;
}
```
:::

---

### [UVa 11067](http://domen111.github.io/UVa-Easy-Viewer/?11067)

撰寫者：Eason

這題的輸出有雷
如果是用 Uva 的
請不要直接複製題目的Sample Output
>題目的Sample Output：![](https://hackmd.io/_uploads/Bygp1W4Pn.png)

>正確的Output：![](https://hackmd.io/_uploads/SJrCy-VP2.png)



<font color = #f9f9f9 >
超可憐，因為這個浪費了整個下午。
</font>

這題數學在學排列組合應該有看過
因為要走捷徑
所以只能往右或往上
基本上對於一個點 $(i,j)$
走到他的所有情況即為
$(i-1,j)$ 的情況加上 $(i,j-1)$ 的情況

可以開一個陣列 $b$
若 $b_{i,j}=1$ 則點 $(i,j)$ 有狼 

可以列出轉移式
$$
\begin{cases}
dp_{i,j} = 1 & i == 0 , j == 0 \\
dp_{i,j} = 0 & b_{i,j}==1 \\
dp_{i,j} = dp_{i-1,j} & j == 0 \\
dp_{i,j} = dp_{i,j-1} & i == 0 \\
dp_{i,j} = dp_{i,j-1}+dp_{i-1,j} & otherwise
\end{cases}
$$

:::spoiler code
```cpp=
#include<bits/stdc++.h>
#define ll long long
#define weakson ios::sync_with_stdio(0), cin.tie(0);
using namespace std;

int main(){
    weakson;

    int w, h;
    while (cin >> w >> h){
        if (w == 0 and h == 0) break;

        vector<vector<bool> > wolf (w + 1, vector<bool> (h + 1));

        int n;
        cin >> n;

        while (n--){
            int x, y;
            cin >> x >> y;
            wolf[x][y] = true;
        }

        vector<vector<ll> > dp (w + 1, vector<ll> (h + 1));
        dp[0][0] = 1;

        for (int i = 0; i <= w; i++){
            for (int j = 0; j <= h; j++){

                if ((i == 0 and j == 0) or wolf[i][j]) continue;

                if (i == 0){
                    dp[i][j] = dp[i][j - 1];
                }
                else if (j == 0){
                    dp[i][j] = dp[i - 1][j];
                }
                else{
                    dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
                }
            }
        }

        ll ans = dp[w][h];

        if (ans == 1){
            cout << "There is one path from Little Red Riding Hood's house to her grandmother's house.\n";
        }
        else if (ans == 0){
            cout << "There is no path.\n";
        }
        else{
            cout << "There are " << ans << " paths from Little Red Riding Hood's house to her grandmother's house.\n";
        }
    }
    return 0;
}
```
:::

---

### [D - Weak Takahashi](https://atcoder.jp/contests/abc232/tasks/abc232_d)

撰寫者：小麥

BFS + DP，跟前面幾題有點像，但這題有可以整張圖被切成兩半，所以要用BFS。
還有BFS要記得設Visited，不然會TLE。

:::spoiler code
```cpp!
#include <bits/stdc++.h>

using namespace std;

// const int mod = 998244353;
int n, m;
vector<vector<int>> arr;
vector<vector<int>> dp;
vector<vector<bool>> visited;
queue<pair<int, int>> bfs;

signed main() 
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);

    cin >> n >> m;
    arr.resize(n + 1, vector<int>(m + 1));
    for (int i = 1; i <= n; i++)
    {
        string row;
        cin >> row;
        for (int j = 1; j <= m; j++)
        {
            if (row[j - 1] == '#')
            {
                arr[i][j] = 1;
            }
        }
    }

    dp.resize(n + 1, vector<int>(m + 1, 0));
    visited.resize(n + 1, vector<bool>(m + 1, 0));
    bfs.push({1, 1});
    while (bfs.size())
    {
        pair<int, int> f = bfs.front();
        bfs.pop();
        if (f.first != n && arr[f.first + 1][f.second] != 1)
        {
            if (!visited[f.first + 1][f.second])
            {
                bfs.push({f.first + 1, f.second});
                visited[f.first + 1][f.second] = true;
            }
        }
        if (f.second != m && arr[f.first][f.second + 1] != 1)
        {
            if (!visited[f.first][f.second + 1])
            {
                bfs.push({f.first, f.second + 1});
                visited[f.first][f.second + 1] = true;
            }
        }
        if (arr[f.first - 1][f.second] == 1)
        {
            dp[f.first][f.second] = dp[f.first][f.second - 1] + 1;
            continue;
        }
        if (arr[f.first][f.second - 1] == 1)
        {
            dp[f.first][f.second] = dp[f.first - 1][f.second] + 1;
            continue;
        }
        dp[f.first][f.second] = max(dp[f.first-1][f.second], dp[f.first][f.second-1]) + 1;
    }

    int result = 0;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            // cout << dp[i][j] << " ";
            result = max(result, dp[i][j]); 
        }
        // cout << "\n";
    }

    cout << result << '\n';

    return 0;
}
```
:::

---

### [zerojudge c001](https://zerojudge.tw/ShowProblem?problemid=c001)

撰寫者：Eason

LCS 裸題

:::spoiler code
```cpp=
#include<bits/stdc++.h>
#define ll long long
#define weakson ios::sync_with_stdio(0), cin.tie(0);
using namespace std;

int main(){
weakson;
string a, b;
while (cin >> a >> b){
    int a_len = a.size();
    int b_len = b.size();
    vector<vector<int> > dp(a_len + 1, vector<int>(b_len + 1, 0));

    for (int i = 1; i <= a_len; i++){
        for (int j = 1; j <= b_len; j++){
            if (a[i - 1] == b[j - 1]){
                dp[i][j] = dp[i - 1][j - 1] + 1;
            }
            else{
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }

    cout << dp[a_len][b_len] << '\n';
}
return 0;
}
```
:::

---

### [cses 1145](https://cses.fi/problemset/task/1145/)

撰寫者：Eason

LIS 裸題
這題要用 $O(n \log n)$ 的作法

:::spoiler code
```cpp=
#include<bits/stdc++.h>
#define ll long long
#define weakson ios::sync_with_stdio(0), cin.tie(0);
using namespace std;

int main(){
    weakson;

    int n;
    cin >> n;

    vector<int> v(n);

    for (int i = 0; i < n; i++) cin >> v[i];

    vector<int> dp;

    for (int i = 0; i < n; i++){
        auto p = lower_bound (dp.begin(), dp.end(), v[i]);
        if (p == dp.end()) dp.push_back (v[i]); // not found
        else dp[p - dp.begin()] = v[i];

        // cout << "dp : ";
        // for (int i : dp) cout << i << ' ';
        // cout << '\n';
    }

    cout << dp.size() << '\n';
}
```
:::






