讀書筆記 - 其他經典動態規劃問題
阿新 • • 發佈:2017-07-16
規模 names padding pac %d can 子序列 計算 化簡
當然,還有LIS, 不過之前總結過了,這次就不貼了
/** 給定兩個字符串s1s2s3...sn和t1t2t3...tm 求這兩個字符串最長的公共子序列的長度 dp[i][j]表示s序列考慮si,t序列考慮tj時的最長公共子序列 狀態轉移方程: s[i] == t[j] : dp[i][j] = dp[i-1][j-1] + 1 s[i] != t[j] : dp[i][j] = max(dp[i-1][j], dp[i][j-1]) */ #include <iostream> #include <stdio.h> #include <string.h> usingnamespace std; const int MAXN = 1000 + 5; char s[MAXN], t[MAXN]; int dp[MAXN][MAXN]; int solve() { scanf("%s%s", s + 1, t + 1); int n = strlen(s + 1); int m = strlen(t + 1); memset(dp, 0, sizeof(dp)); for(int i = 1; i <= n; i++) { for(int j = 1; j <= m; j++) {if(s[i] == t[j]) dp[i][j] = dp[i-1][j-1] + 1; else dp[i][j] = max(dp[i][j-1], dp[i-1][j]); } } printf("%d\n", dp[n][m]); return dp[n][m]; } int main() { solve(); return 0; }
/** 有n種不同大小的數字,每種mi個,判斷是否可以從這些數字中選出若幹個 讓他們的和恰好為K 問題規模: 1 <= n <= 100 1 <= ai,mi <= 1e5 1 <= K <= 1e5 思路一: dp[i][j] 表示前i種物品挑選若幹個是否可以組成和是j 狀態轉移方程: dp[i][j] = dp[i][j] | dp[i-1][j - k*ai] ( j >= k*ai, 0 <= k <= mi) 復雜度O(K*sum(mi)) 還是比較高的 思路二: dp[i][j] 表示前i中物品加和得到j的情況下,mi個ai最多能剩多少個,如果不能得到j,值為-1 狀態轉移方程: dp[i][j] = mi, if dp[i-1][j] >= 0 dp[i][j] = -1, if j < ai || dp[i][j - ai] <= 0 dp[i][j] = dp[i][j - ai] + 1, else ... 復雜度是O(nK)*/ #include <iostream> #include <stdio.h> #include <string.h> using namespace std; const int MAXK = 1e5 + 5; const int N = 100 + 5; int n, K; int m[N], a[N]; int dp1[N][MAXK]; int dp2[MAXK]; void solve1() { memset(dp1, 0, sizeof(dp1)); dp1[0][0] = 1; for(int i = 1; i <= n; i++) { for(int j = 0; j <= K; j++) { for(int k = 0; k <= m[i]; k++) { if(j - k * a[i] >= 0) dp1[i][j] |= dp1[i-1][j - k * a[i]]; } } } if(dp1[n][K]) printf("Yes\n"); else printf("No\n"); } void solve2() { memset(dp2, -1, sizeof(dp2)); dp2[0] = 0; for(int i = 1; i <= n; i++) { for(int j = 0; j <= K; j++) { if(dp2[j] >= 0) dp2[j] = m[i]; else if(j < a[i] || dp2[j - a[i]] <= 0) dp2[j] = -1; else dp2[j] = dp2[j - a[i]] + 1; } } if(dp2[K] >= 0) printf("Yes\n"); else printf("No\n"); } int main() { scanf("%d%d", &n, &K); for(int i = 1; i <= n; i++) scanf("%d", &a[i]); for(int j = 1; j <= n; j++) scanf("%d", &m[j]); solve1(); //solve2(); return 0; }
/** 有n個無區別的物品,將它們劃分成不超過m組,求劃分方法的個數,對M取模 問題規模: 1 <= m <= n <= 1000 2 <= M <= 10000 DP方法: dp[i][j] 表示j的i劃分. j的i劃分,將j劃分成A1,A2,...,Ai,那麽在每一個A中取出一個,就變成了 j-i的i劃分。 如果Ak == 0,那麽就成了j的i-1劃分了 所以有遞推式 dp[i][j] = dp[i][j-i] + dp[i-1][j]; */ #include <iostream> #include <stdio.h> #include <string.h> using namespace std; const int N = 1000 + 5; int n, m; int dp[N][N]; void solve() { dp[0][0] = 1; for(int i = 1; i <= m; i++) { for(int j = 1; j <= n; j++) { if(j - i >= 0) dp[i][j] = dp[i-1][j]; else dp[i][j] = (dp[i-1][j] + dp[i][j-i]) % M; } } printf("%d\n", dp[m][n]); } int main() { scanf("%d%d", &n, &m); solve(); return 0; }
/** 多重集組合數問題 有n種物品,第i種物品有ai個。不同種類的物品可以相互區分,但是同種類的物品是無法區分的 從這些物品中取出m個的話,有多少種取法,方法數模M 問題規模: 1 <= n <= 1000 1 <= m <= 1000 1 <= ai <= 1000 2 <= M <= 10000 //本題的方法學到了一種化簡的思路 動態規劃法: dp[i][j] 表示的是考慮第i種物品,取出了j個的取法數 得到狀態轉移方程: dp[i][j] = sum(dp[i-1][j-k]) ,0 <= k <= ai 且 j >= k 當然,這樣的轉移方程很容易得到,求解也很容易,但是需要三重循環。 時間復雜度是O(sum(ai)*m),這題應該計算量大概是1e9的數量級,有可能會卡過,但是很玄 這個時候需要來化簡這個轉移方程 sum(dp[i-1][j-k]) , 0 <= k <= min(j, ai) sum(dp[i-1][j-k]) = sum(dp[i-1][j-1 - k]) + dp[i-1][j] - dp[i-1][j-1 - ai], 0 <= k <= min(j-1, ai) 這樣就有 dp[i][j] = dp[i][j-1] + dp[i-1][j] - dp[i-1][j-1 - ai] 復雜度就變成O(nm),完全可以應付 */ #include <iostream> #include <string.h> #include <stdlib.h> #include <stdio.h> using namespace std; const int INF = 0x3f3f3f3f; typedef long long LL; const int N = 1000 + 5; const int M = 1000 + 5; int n, m; int a[N]; int dp[N][MAXM]; int M; int read() { scanf("%d%d%d", &n, &m, &M); for(int i = 1; i <= n; i++) { scanf("%d", &a[i]); } } void solve() { for(int i = 0; i <= n; i++) dp[i][0] = 1; for(int i = 1; i <= n; i++) { for(int j = 0; j <= m; j++) { if(j - 1 -a[i] >= 0) dp[i][j] = (dp[i][j-1] + dp[i-1][j] - dp[i-1][j - 1 - a[i]] + M) % M; else dp[i][j] = (dp[i][j-1] + dp[i-1][j]) % M; } } printf("%d\n", dp[n][m]); } int main() { solve(); return 0; }
讀書筆記 - 其他經典動態規劃問題