dp暑假专题 训练记录
Posted Draymonder
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了dp暑假专题 训练记录相关的知识,希望对你有一定的参考价值。
A 回文串的最小划分
题意:给出长度不超过1000的字符串,把它分割成若干个回文字串,求能分成的最少字串数。
#include <iostream> #include <cstdio> #include <string.h> #include <string> using namespace std; const int mod = 1e9 + 7; const int maxn = 1000 + 5; const int INF = 0x3f3f3f3f; typedef long long LL; int dp[maxn]; //根据回文串的特性,两边根据中心对称。于是我们扫描回文串的每个中心,寻找他的回文串,用DP来记录更新 //dp[n] 表示到n为止可以分割出多少个回文串 //如果[Left , Right]是回文串, //dp[Right] = min(dp[ Right ] , dp[Left-1] + 1); int main() { int n; cin >> n; while ( n-- ) { char s[maxn]; cin >> s+1; int L = strlen(s+1); for(int i=1;i <= L;i++) dp[i] = i; for(int i=1;i <= L;i++) { for(int j=i,k=i; j <= L && k > 0 ;j++ ,k--)//这种是aabcc形的回文串 { if(s[j] == s[k] ) dp[j] = min(dp[j],dp[k-1] +1); else break; } for(int j=i+1,k=i; j <= L && k > 0 ;j++,k--)//这种是aabb形的回文串 { if(s[j] == s[k] ) dp[j] = min(dp[j],dp[k-1] +1); else break; } } cout<< dp[L]<<endl; } return 0; }
B - LIS变形
题意:给定一串数字,求一个子串满足一下要求:子串的长度是L=2*n+1,前n+1个数字是严格的递增序列,后n+1个数字是严格的递减序列,例如123454321就是满足要求的一个子串,
输出满足要求的最长的L
思路:正着走一遍LIS,再倒着走一遍LIS,a1[i] 表示正着走的 到i的最长递增子序列的长度,a2[i] 表示倒着着走的 到i的最长递增子序列的长度
//本题 二分 + LIS 需要巩固一下
#include <iostream> #include <cstdio> using namespace std; const int mod = 1e9 + 7; const int maxn = 10000 + 5; const int INF = 0x3f3f3f3f; typedef long long LL; int n,len; int s1[maxn],s2[maxn]; int a1[maxn],a2[maxn]; int B[maxn]; int b_s(int k) { int ans = 0; int l = 0,r = len-1; //这里注意 右区间是 len-1 while ( l <= r) { int mid = (l+r)/2; if(B[mid] >= k ) ans = mid,r = mid-1; //如果k<= B[mid] 往左边查找 else l = mid +1; } return ans ; } void Lis(int *s,int *a) //a数组存取到i位 的最长递增子序列个数 { B[0] = s[0]; len = 1; for(int i=0;i < n;i++) { if(s[i] > B[len-1]) { B[len++] = s[i]; } else{ int pos = b_s (s[i]); //二分找s[i]的插入位置 B[pos] = s[i]; } a[i] = len; } } int main() { while (cin >> n) { for(int i=0;i < n;i++){ cin >> s1[i]; s2[n-1-i] = s1[i];//把序列倒着读 } Lis(s1,a1), Lis(s2,a2); //longest increasing substring 最长递增子序列函数 int ans = 0; for(int i=0;i < n;i++) { ans = max(ans, min(a1[i],a2[n-1-i])); //ans 是 ans 与 当前位 左右最长递增 和 最长递减的较小者 } cout<< 2*ans -1 <<endl; } return 0; }
C - 区间dp or LCS的变形
题意:给你一个字符串, 求最长的回文子序列, 若答案不唯一, 则输出字典序最小的
思路1:区间dp, dp[i][j]表示字符串s[i]到s[j]构成回文子序列学删除的最小字符数, path[i][j] 字符串s[i]到s[j]可构成的最长回文子序列
#include <iostream> #include <cstdio> #include <string.h> #include <string> using namespace std; const int mod = 1e9 + 7; const int maxn = 1000 + 5; const int INF = 0x3f3f3f3f; typedef long long LL; //区间dp dp[i][j] 表示字符串s[i]到s[j]构成回文子序列学删除的最小字符数 //path[i][j] 字符串s[i]到s[j]可构成的最长回文子序列 string path[maxn][maxn]; int dp[maxn][maxn]; char s[maxn]; int main() { while (cin >> s+1) { int L = strlen(s+1); for(int i=L;i >= 1;i--) //这个查找是从 左下到右上进行的 { dp[i][i] = 0;//i到i不用删除 path[i][i] = s[i];//i到i的最长回文子序列就是本身 for(int j=i+1;j <= L;j++) { if(s[i] == s[j]) //就是 s[i][j]这两个字符相同 可以构成回文串 { dp[i][j] = dp[i+1][j-1]; //所以这时候 需要删除的就是左下角的 path[i][j] = s[i] + path[i+1][j-1] +s[j];//只有这一步是构造回文串的 } else if(dp[i+1][j] > dp[i][j-1]) { dp[i][j] = dp[i][j-1] + 1; path[i][j] = path[i][j-1]; } else if(dp[i+1][j] < dp[i][j-1]) { dp[i][j] = dp[i+1][j] + 1; path[i][j] = path[i+1][j]; } else{ dp[i][j] = dp[i+1][j] + 1; path[i][j] = min(path[i][j-1], path[i+1][j]); } } /* for(int a = 1;a<=L;a++) { for(int b= 1;b<=L;b++) { cout<< path[a][b]<<" "; } cout<<endl; } cout<<"----------" <<endl; */ } cout << path[1][L] <<endl; } return 0; }
思路2:转化为lcs, 用结构体进行保存,一维只长度, 一维指构成的字符串
我们都知道把一个字符串逆序后和原字符串进最长公共子序列,可以计算出它的最长回文串长度。这题不仅要输出回文串,而且还要求是字典序最小的,所以挺难搞的。
设str1是正序字符串,str2是逆序后的字符串
f[i][j].len 表示str1的前i位,str2的前j位,最长公共子串的长度
f[i][j].str 表示str1的前i位,str2的前j位,最长公共子串的最小字典序的字符串
状态转移和正常的LCS差不多,只不过增加了记录字典序最小的字符串
但是最终的f[i][j].str却并不一定是答案,因为计算出来的最长公共子序列不一定就是回文串
例如:
kfclbckibbibjccbej
jebccjbibbikcblcfk
bcibbibc是他们的LCS,但是却不是回文串
但是它的前len/2个一定是回文串的前半部分
#include <cstdio> #include <string> #include <cstring> #include <iostream> #include <algorithm> using namespace std; using namespace std; const int mod = 1e9 + 7; const int maxn = 1000 + 5; const int INF = 0x3f3f3f3f; typedef long long LL; struct node{ int len; string s; }dp[maxn][maxn]; char s1[maxn],s2[maxn]; int main () { while (cin >> s1+1) { int L = strlen(s1+1); strcpy(s2+1,s1+1); reverse(s2+1,s2+1+L); for(int i=1;i <= L;i++) { for(int j=1;j <= L;j++) { if(s1[i] == s2[j]){ dp[i][j].len = dp[i-1][j-1].len+1; dp[i][j].s = dp[i-1][j-1].s + s1[i]; } else if(dp[i-1][j].len < dp[i][j-1].len){ dp[i][j].len = dp[i][j-1].len; dp[i][j].s = dp[i][j-1].s; } else if(dp[i-1][j].len > dp[i][j-1].len){ dp[i][j].len = dp[i-1][j].len; dp[i][j].s = dp[i-1][j].s; } else{ dp[i][j].len = dp[i-1][j].len; dp[i][j].s = min( dp[i-1][j].s ,dp[i][j-1].s); } } } string s = dp[L][L].s; int l =dp[L][L].len; if( l & 1) //l是奇数,所以字符串长度是偶数 { for(int i=0;i<l/2;i++) cout<<s[i]; for(int i=l/2;i>=0;i--) cout<<s[i]; cout<<endl; } else{ for(int i=0;i<l/2;i++) cout<<s[i]; for(int i=l/2-1;i>=0;i--) cout<<s[i]; cout<<endl; } } return 0; }
以上是关于dp暑假专题 训练记录的主要内容,如果未能解决你的问题,请参考以下文章