--- tags: 109_FDCS, report --- <!-- ## 複習 快速冪code ```cpp= constexpr int mod = 998244353; long long pow(long long a, long long b){ long long ret = 1; for(; /*...<1>...*/; b = /*...<2>...*/, a = /*...<3>...*/) if(/*...<4>...*/) ret = /*...<5>...*/; return ret; } ``` :::spoiler <1> `b` ::: :::spoiler <2> `b >> 1` ::: :::spoiler <3> `a * a % mod` ::: :::spoiler <4> `b & 1` ::: :::spoiler <5> `ret * a % mod` ::: --> # Dynamic Programming - 空間換時間 <!--動態規劃在尋找有很多重疊子問題的情況的最佳解時有效。它將問題重新組合成子問題。為了避免多次解決這些子問題,它們的結果都逐漸被計算並被儲存,從簡單的問題直到整個問題都被解決。因此,動態規劃儲存遞迴時的結果,因而不會在解決同樣的問題時花費時間。--> - 由部分最佳解推出整體最佳解 <!--動態規劃只能應用於有最佳子結構的問題。最佳子結構的意思是局部最佳解能決定全域最佳解(對有些問題這個要求並不能完全滿足,故有時需要引入一定的近似)。簡單地說,問題能夠分解成子問題來解決。--> ## fibonacci sequence #### 遞迴 最直覺的解法 ```cpp= long long fib(int x) { if (x < 2) return x; return fib(x - 1) + fib(x - 2); } ``` 時間複雜度$O(\phi^n)$ ### 轉移式 {$$dp_i=\begin{cases}i&i<2\\dp_{i-1}+dp_{i-2}&otherwise\end{cases}$$} #### DP解 將答案存起來避免重複計算 ```cpp= long long ans[100]; long long fib(int x) { if (x < 2) return x; return ans[x] ? ans[x] : (ans[x] = fib(x - 1) + fib(x - 2)); } ``` 時間複雜度$O(n)$, 空間複雜度$O(n)$ #### 滾動DP ```cpp= long long fib(int x) { vector<int> v{0, 1}; for (int i = 2; i <= x; i++) v[i & 1] += v[~i & 1]; return v[x & 1]; } ``` 時間複雜度$O(n)$, 空間複雜度$O(1)$ ### 例題 [a069: I Love You 3000](http://203.64.191.163/ShowProblem?problemid=a069) [a244: pD Minecraft 1.15 嗡嗡蜂群更新 上線啦!!!!](http://203.64.191.163/ShowProblem?problemid=a244) [a372: 又來爬樓梯](http://203.64.191.163/ShowProblem?problemid=a372) --- ## Longest Common Subsequence(LCS) ### 轉移式 {$$dp_{i,j}=\begin{cases}1+dp_{i-1,j-1}&a_i=b_j\\max(dp_{i-1,j},dp_{i,j-1})&otherwise\end{cases}$$} #### DP解 ```cpp= vector<vector<int>> dp(a.size() + 1, vector<int>(b.size() + 1)); for (size_t i = 0; i < a.size(); i++) for (size_t j = 0; j < b.size(); j++) { if (a[i] == b[j]) dp[i + 1][j + 1] = 1 + dp[i][j]; else dp[i + 1][j + 1] = max(dp[i][j + 1], dp[i + 1][j]); } cout << dp[a.size()][b.size()] << endl; ``` 時間複雜度$O(nm)$, 空間複雜度$O(nm)$ #### 滾動DP ```cpp= auto& a = max(inputa, inputb, [](auto& x, auto& y) {return x.size() < y.size();}); auto& b = min(inputa, inputb, [](auto& x, auto& y) {return x.size() < y.size();}); vector<vector<int>> dp(2, vector<int>(b.size() + 1)); for (size_t i = 0; i < a.size(); i++) for (size_t j = 0; j < b.size(); j++) { if (a[i] == b[j]) dp[~i & 1][j + 1] = 1 + dp[i & 1][j]; else dp[~i & 1][j + 1] = max(dp[i & 1][j + 1], dp[~i & 1][j]); } cout << dp[a.size() & 1][b.size()] << endl; ``` 時間複雜度$O(nm)$, 空間複雜度$O(min(n,m))$ ### 例題 [c001: 10405 - Longest Common Subsequence](https://zerojudge.tw/ShowProblem?problemid=c001) --- ## 路徑總數 ### 轉移式 {$$dp_{i,j}=\begin{cases}1&i=1\ or\ j=1\\dp_{i-1,j}+dp_{i,j-1}&otherwise\end{cases}$$} #### DP解 ```cpp= vector<vector<int>> dp(n, vector<int>(m, 1)); for (int i = 1; i < n; i++) for (int j = 1; j < m; j++) dp[i][j] = (dp[i - 1][j] + dp[i][j - 1]) % mod; cout << dp.back().back() << endl; ``` 時間複雜度$O(nm)$, 空間複雜度$O(nm)$ #### 滾動DP ```cpp= auto n = max(a, b); auto m = min(a, b); vector<vector<int>> dp(2, vector<int>(m, 1)); for (int i = 1; i < n; i++) for (int j = 1; j < m; j++) dp[i & 1][j] = dp[~i & 1][j] + dp[i & 1][j - 1]; cout << dp[~n & 1].back() << endl; ``` 時間複雜度$O(nm)$, 空間複雜度$O(min(n,m))$ ### 例題 [d198: 00825 - Walking on the Safe Side](https://zerojudge.tw/ShowProblem?problemid=d198) [d378: 最小路徑](https://zerojudge.tw/ShowProblem?problemid=d378) --- ## 其他DP ## [a227: B.B.K.K.B.K.K.](http://203.64.191.163/ShowProblem?problemid=a227) ### 轉移式 {$$dp_i=\begin{cases}2&i=1\\6&i=2\\2\times (dp_{i-1}+dp_{i-2})&otherwise\end{cases}$$} #### DP解 ```cpp= vector<int> v{1, 2, 6}; for (int i = 3; i <= n; i++) v[i % 3] = (v[(i - 1) % 3] + v[(i - 2) % 3]) << 1; cout << v[n % 3] << endl; ``` 時間複雜度$O(n)$, 空間複雜度$O(1)$ ### 例題 [c878: 107北二5.議會質詢紀錄](https://zerojudge.tw/ShowProblem?problemid=c878) --- ## 樹上DP 這不會考 :poop: ### 例題 [20200704 APCS P4](/@emanlaicepsa/APCS202007?fbclid=IwAR0t4NRoIV4fBzrmpEXxCj535AgkUx9l90hrZ9GjMrOTXg-WtuR2OKZYkbI#p4) :::spoiler 範例code ```cpp= //沒在OJ上試過,可能有些小bug #include<iostream> #include<vector> #include<functional> using namespace std; const string dic = "AUCG"; struct node { string val; vector<long long> ch, dp; }; int main() { int n, m, a, b, root; cin >> n >> m; vector<node> v(n); for (int i = 0; i != n; i++) { cin >> a >> b >> v[a - 1].val; if (a == b) root = a - 1; else v[b - 1].ch.emplace_back(a - 1); } long long sum = 0; for (int k = 0; k != m; k++) { static function<void(int)> dfs = [&](int x) { v[x].dp.assign(4, 0); for (const auto& i : v[x].ch) dfs(i); for (int i = 0; i != 4; i++) { if (v[x].val[k] != '@' && v[x].val[k] != dic[i]) { v[x].dp[i] = 1e9; continue; } for (auto& j : v[x].ch) { long long minn = 1e9; for (int l = 0; l != 4; l++) minn = min(minn, v[j].dp[l] + (l != i)); v[x].dp[i] += minn; } } }; dfs(root), sum += min({v[root].dp[0], v[root].dp[1], v[root].dp[2], v[root].dp[3]}); } cout << sum << endl; return 0; } ``` :::