字符串类dp的题目总结
熟练掌握回文串吧,大致有dp或者模拟类的吧
①dp+预处理,懂得如何枚举回文串(一)
②dp匹配类型的题目(二)
③dp+预处理 子串类型 (三)
④字符串的组合数(四)
一:划分成回文串 UVA11584 紫书275 dp+预处理
题目大意:输入一个字符串,把他划分成尽量少的回文串,能划分成几个?
思路:定义dp[i]表示i之间最少能弄成几个回文串,然后枚举1~j(j < i),然后在枚举j的时候判断目前是否为回文串,这样复杂度为n^3.因此我们提前用n^2判断好是否为回文串就行了。回文串的方法就是枚举中心,但是枚举中心也是有技巧的!
lrj老师的代码太牛了!!!第一次见到这么写的!!!具体看代码上的注释吧!!!
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 // UVa11584 Partitioning by Palindromes 2 // Rujia Liu 3 // This code is slightly different from the book. 4 // It uses memoization to judge whether s[i..j] is a palindrome. 5 #include<cstdio> 6 #include<cstring> 7 #include<algorithm> 8 using namespace std; 9 10 const int maxn = 1000 + 5; 11 int n, kase, vis[maxn][maxn], p[maxn][maxn], d[maxn]; 12 char s[maxn]; 13 14 int is_palindrome(int i, int j) { 15 if(i >= j) return 1; 16 if(s[i] != s[j]) return 0; 17 //p[i][j]定义的是i到j是否为回文串 18 if(vis[i][j] == kase) return p[i][j]; 19 vis[i][j] = kase;//我去,还能这么玩,在dp的过程中预处理,然后用kase区分 20 p[i][j] = is_palindrome(i+1, j-1); 21 return p[i][j]; 22 } 23 24 int main() { 25 int T; 26 scanf("%d", &T); 27 memset(vis, 0, sizeof(vis)); 28 for(kase = 1; kase <= T; kase++) { 29 scanf("%s", s+1); 30 n = strlen(s+1); 31 d[0] = 0; 32 for(int i = 1; i <= n; i++) { 33 d[i] = i+1; 34 for(int j = 0; j < i; j++) 35 if(is_palindrome(j+1, i)) d[i] = min(d[i], d[j] + 1); 36 } 37 printf("%d\n", d[n]); 38 } 39 return 0; 40 }
然后我的for循环和lrj老师的不一样
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //看看会不会爆int!数组会不会少了一维! 2 //取物问题一定要小心先手胜利的条件 3 #include <bits/stdc++.h> 4 using namespace std; 5 #define LL long long 6 #define ALL(a) a.begin(), a.end() 7 #define pb push_back 8 #define mk make_pair 9 #define fi first 10 #define se second 11 const int maxn = 1000 + 5; 12 char atlas[maxn]; 13 int dp[maxn]; 14 bool p[maxn][maxn], vis[maxn][maxn]; 15 16 bool dfs(int i, int j){ 17 if (i == j || j < i) return true; 18 if (atlas[i] != atlas[j]) return false; 19 if (vis[i][j]) return p[i][j]; 20 vis[i][j] = true; 21 p[i][j] = dfs(i + 1, j - 1); 22 return p[i][j]; 23 } 24 25 int main(){ 26 int t; cin >> t; 27 while (t--){ 28 memset(vis, 0, sizeof(vis)); 29 scanf("%s", atlas + 1); 30 int len = strlen(atlas + 1); 31 for (int i = 1; i <= len; i++){ 32 for (int j = 1; j < i; j++){ 33 p[i][j] = dfs(j, i); 34 } 35 } 36 memset(dp, 0x3f, sizeof(dp)); 37 dp[0] = 0; 38 for (int i = 1; i <= len; i++){ 39 dp[i] = dp[i - 1] + 1;//我定义目前这个字符单独组成回文串 40 for (int j = 1; j <= i; j++){ 41 if (p[i][j]){ 42 dp[i] = min(dp[i], dp[j - 1] + 1); 43 } 44 } 45 } 46 printf("%d\n", dp[len]); 47 } 48 return 0; 49 }
学习之处:回文串有两种,一种是abba,另一种是cbabc。然后如何枚举这两种本来是应该要分类讨论的,我的想法是枚举i-j,然后用n^2的想法
二:括号序列 UVA1626 紫书278
题目大意:给你几个合法的串的定义,只包含()[]这四个符号,加上多少的(、)、[、]能使给定的串变成一个合法的串
思路:定义dp[i][j]表示从i~j成功匹配所需要添加的最少的符号的个数使之变成合法的串。
首先我们根据dfs来,可以发现,如果是dfs(i, j),我们要尝试在每一种i~j中进行分割,然后如果i和j是一个合法的串,那么就直接dfs(i-1, j-1),不然就进行分割,然后记得,当最后只有一个符号的时候就假定要再加一个字符即可。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//看看会不会爆int!数组会不会少了一维! //取物问题一定要小心先手胜利的条件 #include <bits/stdc++.h> using namespace std; #define LL long long #define ALL(a) a.begin(), a.end() #define pb push_back #define mk make_pair #define fi first #define se second const int inf = 0x3f3f3f3f; const int maxn = 100 + 5; char s[maxn]; int dp[maxn][maxn];//从最右边i开始,到j匹配成功的最小的数目 bool match(int i, int j){ if (s[i] == '(' && s[j] == ')') return true; if (s[i] == '[' && s[j] == ']') return true; return false; } void display(int i, int j){ if (i == j) { if (s[i] == '(' || s[i] == ')') printf("()"); else printf("[]"); return ; } int ans = dp[i][j]; if (match(i, j) && ans == dp[i + 1][j - 1]){ printf("%c", s[i]); display(i + 1, j - 1); printf("%c", s[j]); return ; } for (int k = i; k <= j - 1; k++){ if (ans == dp[i][k] + dp[k + 1][j]){ display(i, k); display(k + 1, j); return ; } } } void readline(char* S) { fgets(S, maxn, stdin); } int main(){ int T; readline(s); sscanf(s, "%d", &T); readline(s); while (T--){ readline(s); int n = strlen(s) - 2; for (int i = 0; i <= n; i++){ dp[i][i] = 1; } for (int i = n - 1; i >= 0; i--){ for (int j = i + 1; j <= n; j++){ dp[i][j] = inf; if (match(i, j)) dp[i][j] = min(dp[i][j], dp[i + 1][j - 1]); for (int k = i; k <= j - 1; k++){//如果从k=i+1开始枚举的话,就需要我下面的两句话,但是弊端就是输出的时候要多很多的条件。不过从i开始枚举的话就不需要了 //if (match(i, k)) dp[i][j] = min(dp[i][j], dp[i + 1][k - 1] + dp[k + 1][j]); //if (match(k, j)) dp[i][j] = min(dp[i][j], dp[i][k - 1] + dp[k + 1][j - 1]); ///上面两句话有没有无所谓,因为你的j是会枚举到你这些列举的情况的 dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j]); } } } display(0, n); printf("\n"); if(T) printf("\n"); readline(s); //printf("%d\n", dp[0][n]); } return 0; }
学习之处:定义的学习,dp边界的找寻通过dfs来进行的,如何路径输出
三:http://codeforces.com/contest/477/problem/C codeforces 272 div1 C
题目大意:给你字符串s和p,从s删除0~|s|个字符,最多能形成多少个不重叠的字串p
思路:定义dp[i][j]表示前i个字符删除j个,定义cal(i)表示从第i个开始往前数,删除几个才能组合成一个子串p。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //看看会不会爆int!数组会不会少了一维! 2 //取物问题一定要小心先手胜利的条件 3 #include <bits/stdc++.h> 4 using namespace std; 5 #define LL long long 6 #define ALL(a) a.begin(), a.end() 7 #define pb push_back 8 #define mk make_pair 9 #define fi first 10 #define se second 11 const int maxn = 2000 + 5; 12 char s[maxn], p[maxn]; 13 int lens, lenp, pos; 14 int dp[maxn][maxn];//对当前位置i,删除j个能产生的最大匹配数 15 16 int cal(int ps){ 17 int lp = lenp; 18 int cnt = 0; 19 for (int i = ps; i >= 1; i--){ 20 if (s[i] == p[lp]){ 21 lp--; 22 if (lp == 0){ 23 pos = i; 24 return cnt; 25 } 26 } 27 else cnt++; 28 } 29 } 30 31 int main(){ 32 scanf("%s", s + 1); lens = strlen(s + 1); 33 scanf("%s", p + 1); lenp = strlen(p + 1); 34 35 for (int i = lenp; i <= lens; i++){ 36 int cnt = cal(i); 37 for (int j = 0; j < i; j++){ 38 dp[i][j] = max(dp[i][j], dp[i - 1][j]); 39 if (j >= cnt && pos - 1 >= j - cnt){ 40 dp[i][j] = max(dp[i][j], dp[pos - 1][j - cnt] + 1); 41 } 42 } 43 } 44 for (int i = 0; i <= lens; i++){ 45 printf("%d%c", dp[lens][i], i == lens ? '\n' : ' '); 46 } 47 return 0; 48 }
四:http://codeforces.com/contest/476/problem/B codeforces 272 div2 B
题目大意:给你两个串,串1是由+或-组合而成,串二是由+、-、?组合而成的。?可以随便改变成+或-,问有多少的概率能使得两个串的+和-数目相等
思路:先求出串1串二中的+-?的数目,然后让串一的+-减去串二的+-,如果小于0就直接概率为0,反之进行dp即可。定义dp[i][j],表示一共有i+j个?,生成i个+,j个-。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //看看会不会爆int!数组会不会少了一维! 2 //取物问题一定要小心先手胜利的条件 3 #include <bits/stdc++.h> 4 using namespace std; 5 #define LL long long 6 #define ALL(a) a.begin(), a.end() 7 #define pb push_back 8 #define mk make_pair 9 #define fi first 10 #define se second 11 const int maxn = 100 + 5; 12 char s1[maxn], s2[maxn]; 13 double dp[maxn][maxn]; 14 15 int id(char c){ 16 if (c == '+') return 0; 17 if (c == '-') return 1; 18 if (c == '?') return 2; 19 } 20 21 int main(){ 22 scanf("%s%s", s1, s2); 23 int len = strlen(s1); 24 for (int i = 0; i < len; i++){ 25 s1[i] = id(s1[i]); 26 s2[i] = id(s2[i]); 27 } 28 sort(s1, s1 + len); 29 sort(s2, s2 + len); 30 int t10 = upper_bound(s1, s1 + len, 0) - lower_bound(s1, s1 + len, 0); 31 int t11 = upper_bound(s1, s1 + len, 1) - lower_bound(s1, s1 + len, 1); 32 33 int t20 = upper_bound(s2, s2 + len, 0) - lower_bound(s2, s2 + len, 0); 34 int t21 = upper_bound(s2, s2 + len, 1) - lower_bound(s2, s2 + len, 1); 35 int t22 = upper_bound(s2, s2 + len, 2) - lower_bound(s2, s2 + len, 2); 36 37 t10 -= t20, t11 -= t21; 38 if (t10 < 0 || t11 < 0){ 39 printf("0.000000000000\n"); 40 return 0; 41 } 42 double ans = 0; 43 dp[0][0] = 1.0; 44 for (int i = 1; i <= t22; i++){//有i个 45 for (int j = 0; j <= i; j++){//有j个+ 46 int l = i - j; 47 if (j == 0) dp[j][l] = dp[j][l - 1] * 0.5; 48 else if (l == 0) dp[j][l] = dp[j - 1][l] * 0.5; 49 else dp[j][l] = max(dp[j - 1][l] * 0.5 + dp[j][l - 1] * 0.5, dp[j][l]); 50 } 51 } 52 printf("%.12f\n", dp[t10][t11]); 53 return 0; 54 }
五:
六:
七: