题意:
给定一个字符串,和一个目标串,然后有一支画笔,每次可以将某一个区间内全部字符刷成你想要的字符,但是是一样的字符,比如 zzzzzfzzzzz,我可以用画笔把某一连续段刷成任意字符,我可以刷成zzaaafzzzzz,我刷了三个a。
问:最少刷几次,可以把给定字符串刷成目标串?
例如zzzzzfzzzzz,长度为11,下标看做0~10
先将0~10用画笔刷一次,变成aaaaaaaaaaa
1~9刷一次,abbbbbbbbba
2~8: abcccccccba
3~7: abcdddddcba
4~6: abcdeeedcab
5: abcdefedcab
这样就6次,变成了s2串了
每次刷一个区间
思路:
动态规划区间dp,第一次碰这种题很难理解。主要过程就是把大问题化成一个个 的小问题,从小问题逐渐把大问题递推出来
我们先从区间长度为1开始,统计每个长度为1的区间需要刷的次数,然后就能利用这些数据,去递推出长度为2的各个区间的次数。以此类推。
具体实现看代码:
#include<stdio.h> #include<string.h> int min(int a,int b){ return a<b?a:b; } int dp[110][110];//dp[i][j]就代表区间[i,j],包含i,j int dp1[110]; char s[110],c[110];//给定字符串,目标串 int main() { while(~scanf("%s%s",s,c)) { int len=strlen(s); memset(dp,0,sizeof(dp)); for(int l=1;l<=len;l++) //区间长度,从小到大递推 for(int i=0;i<len-l+1;i++) //假设一个左端点,则右端点j=i+l-1,且<len; { int j=i+l-1;//右端点 //现在记大区间为dp[i][j](未知),小区间dp[i+1][j](已求出) dp[i][j]=dp[i+1][j]+1;//刷完小区间,加一次把i这个字符刷掉 for(int k=i+1;k<=j;k++) //扫描小区间 { if(c[i]==c[k])//小区间内某字符等于大区间左端点 { //再把小区间分开,i这个字符就可以跟随dp[i+1][k]一起刷掉 //再加上dp[k+1][j]即可 //与原数值比较大小 dp[i][j]=min(dp[i][j],dp[i+1][k]+dp[k+1][j]); } } } //到了这里还没有结束!! /*因为上面的dp是按左端点处理的, 当区间长度大于1时,左端点就永远到不了字符串的末尾 末尾的dp也就没有全面进行 */ //下面单独对右端点进行动态规划 for(int i=0;i<len;i++) dp1[i]=dp[0][i];//用dp1[i]表示区间[0][i]的最优解 for(int i=0;i<len;i++) { if(s[i]==c[i])//该字符无需更改 { //那么只需要改该字符左边的字符 if(i==0)//特殊情况 dp1[i]=0; else dp1[i]=dp1[i-1];//因为第i个字符不需要做什么 } else for(int k=0;k<i;k++) //一定要从0开始, dp1[i]=min(dp1[i],dp1[k]+dp[k+1][i]); } printf("%d\n",dp1[len-1]); } return 0; }
模板:
memset(dp,0,sizeof(dp)); //二维 int i,j,l,k; for(l = 2; l <= n; ++l) //假设区间长度,递推 { for(i = 1; i <= n - l + 1; ++i) //左端点 { j = i + l - 1; //右端点 dp[i][j] = 2100000000; //初值(有时初值跟之前的状态有关系,不一定常数) for(k = i; k < j; ++k) //找中转站 { dp[i][j]=min(dp[i][j],dp[i][k] + dp[k + 1][j] +权值); } } } printf("%d\n", dp[1][n]);
有时候,可能区间左端点访问不到末尾,从而得不到最优解,需要再动态规划一次,比如上面的例题。