关于回文串的DP问题
问题1:插入/删除字符使得原字符串变成一个回文串且代价最小
poj 3280 Cheapest Palindrome
题意:给出一个由m中字母组成的长度为n的串,给出m种字母添加和删除花费的代价,求让给出的串变成回文串的代价。
Sol:
- 插入和删除等价,因此只需要保留 min(插入代价,删除代价)作为调整字符串的代价
- 如果 s[i]==s[j],那么将区间(i,j)变为回文串的代价和将区间(i+1,j-1)变为回文串的代价相同,因为此时不需要修改
- 如果不同,必然要将 s[i]和s[j]改为同一字符
- 第一种情况是,想要将(i,j)变为回文串,可以是在(i+1,j)已是回文串的基础上,在j后面添加字符 s[i],或者直接将i处的字符 s[i] 删掉,取代价小的操作即可
- 另一种情况是,如果(i,j-1)是回文串,可以将j处的字符删掉或在i前面填加字符s[j],同样取代价小的方式操作
Code:提供几种不同的写法,加深理解
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <stdio.h> 2 #include <string.h> 3 #define mem(a) memset(a,0,sizeof(a)) 4 #define MIN(a,b) ((a) < (b) ? (a) : (b)) 5 6 int DP[2005][2005],cost[30],N,M; 7 char str[2005]; 8 9 int main() 10 { 11 while(~scanf("%d%d", &M, &N)) 12 { 13 mem(DP); mem(str); mem(cost); 14 scanf("%s%*c",str); 15 char ch; int x, y; 16 for(int i=0;i<M;i++) 17 { 18 scanf("%c %d %d%*c", &ch, &x, &y); 19 cost[ch-'a'] = MIN(x,y); 20 } 21 for(int i=1;i<N;i++) 22 { 23 for(int j=i-1;j>=0;j--) 24 { 25 DP[j][i] = MIN(DP[j+1][i]+cost[str[j]-'a'], DP[j][i-1]+cost[str[i]-'a']); 26 if(str[i] == str[j])DP[j][i] = MIN(DP[j][i],DP[j+1][i-1]); 27 } 28 } 29 printf("%d\n", DP[0][N-1]); 30 } 31 return 0; 32 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<algorithm> 3 using namespace std; 4 typedef pair<int, int> P; 5 #define ad first 6 #define de second 7 int n, m, dp[2005][2005]; 8 char s[2005]; 9 P ch[28]; //ch[ch-'a'].ad代表add一个ch的代价,ch[ch-'a'].de代表delete一个ch的代价 10 int main() 11 { 12 cin >> n >> m >> s; 13 for (int i = 1; i <= n; i++) 14 { 15 char Ch; 16 cin >> Ch; 17 cin >> ch[Ch - 'a'].ad >> ch[Ch - 'a'].de; 18 } 19 for (int i = 1; i <= m; i++) 20 dp[i][i] = 0; 21 for (int i = 1; i < m; i++) //注意for循环要实现从短串到长串的过渡,这里i代表长度为i+1 22 for (int j = 0; j + i < m; j++) 23 if (s[j] == s[j + i]) dp[j][j + i] = dp[j + 1][j + i - 1]; 24 else 25 { 26 int aa = dp[j + 1][j + i] + min(ch[s[j] - 'a'].ad, ch[s[j] - 'a'].de); 27 int bb = dp[j][j + i - 1] + min(ch[s[j + i] - 'a'].ad, ch[s[j + i] - 'a'].de); 28 dp[j][j + i] = min(aa, bb); 29 } 30 printf("%d\n", dp[0][m - 1]); 31 return 0; 32 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <cstdio> 2 #include <string> 3 #include <iostream> 4 #include <algorithm> 5 #include <cstring> 6 using namespace std; 7 const int N = 200; 8 const int M = 2500; 9 int add[N]; 10 int dp[M][M]; 11 12 int main() 13 { 14 int n,m; 15 string s; 16 while(~scanf("%d%d",&n,&m)) 17 { 18 cin>>s; 19 char c;int x,y; 20 for(int i=0;i<n;i++) 21 { 22 cin>>c>>x>>y; 23 add[c]=min(x,y); 24 } 25 memset(dp,0,sizeof(dp)); 26 for(int k=1;k<s.size();k++) 27 { 28 for(int i=0,j=k;j<s.size();i++,j++) 29 { 30 dp[i][j]=0x3f3f3f3f; 31 if(s[i]==s[j]) 32 dp[i][j]=dp[i+1][j-1]; 33 else 34 { 35 dp[i][j]=min(dp[i+1][j] + add[s[i]],dp[i][j]); 36 dp[i][j]=min(dp[i][j-1] + add[s[j]],dp[i][j]); 37 } 38 } 39 } 40 printf("%d\n",dp[0][s.size()-1]); 41 } 42 return 0; 43 }
问题2:插入最少多少个字符使得原字符串变成一个回文串
poj 1159 Palindrome
Sol:
首先第一种方法是:
这道题相当于是上一个题中的修改代价为1的情况
因此列出方程:
从上面的分析可以看出,这个问题的实质是求最长公共子序列,只是这两个序列分别是串S的前一部分和串S后一部分的逆序列。
由此引出第二种方法
第二种方法:
先说结论:设原序列S的逆序列为S',最少需要补充的字母数 = 原序列S的长度-S和S'的最长公共子串长度
最后这道题需要对内存进行优化
Code:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 5 using namespace std; 6 7 #define MIN(a, b) ((a) < (b) ? (a) : (b)) 8 9 #define MAXSIZE 5005 10 11 //开始没有考虑内存问题,使用了int型,超内存限制,也可使用滚动数组解决 12 unsigned short d[MAXSIZE][MAXSIZE]; 13 14 int ToPalindrome(char *s, int n) 15 { 16 int i, j, k; 17 //只有一个字符时,不需要添加字符 18 for (i = 0; i < n; i++) 19 { 20 d[i][i] = 0; 21 } 22 //串长度为2时 23 for (i = 1; i < n; i++) 24 { 25 if (s[i-1] == s[i]) 26 { 27 d[i-1][i] = 0; 28 } 29 else 30 { 31 d[i-1][i] = 1; 32 } 33 } 34 35 //串长度递增 36 for (k = 2; k < n; k++) 37 { 38 for (i = 0, j = k; j < n; i++, j++) 39 { 40 if (s[i] == s[j]) 41 { 42 d[i][j] = d[i+1][j-1]; 43 } 44 else 45 { 46 d[i][j] = MIN(d[i][j-1], d[i+1][j]) + 1; 47 } 48 } 49 } 50 return d[0][n-1]; 51 } 52 53 int main(void) 54 { 55 char str[MAXSIZE]; 56 57 int n; 58 while (scanf("%d", &n) != EOF) 59 { 60 getchar(); 61 gets(str); 62 printf("%d\n", ToPalindrome(str, n)); 63 } 64 return 0; 65 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstring> 2 #include<cstdio> 3 #include<algorithm> 4 #include<iostream> 5 using namespace std; 6 7 const int N=5010; 8 int n; 9 char a[N],b[N]; 10 int f[2][N]; 11 12 int main(){ 13 while(scanf("%d",&n)!=EOF){ 14 scanf("%s",a+1); 15 for(int i=1;i<=n;++i) 16 b[i]=a[n-i+1]; 17 memset(f,0,sizeof(f)); 18 for(int i=1;i<=n;++i) 19 for(int j=1;j<=n;++j) 20 if(a[i]==b[j]) f[i%2][j]=f[(i-1)%2][j-1]+1; 21 else f[i%2][j]=max(f[(i-1)%2][j],f[i%2][j-1]); 22 printf("%d\n",n-f[n%2][n]); 23 } 24 return 0; 25 }