当然,还有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> using namespace 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 ", 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 "); else printf("No "); } 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 "); else printf("No "); } 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 ", 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 ", dp[n][m]); } int main() { solve(); return 0; }