HDU 2476 String painter
题意:
给定一个字符串,和一个目标串,然后有一支画笔,每次可以将某一个区间内全部字符刷成你想要的字符,但是是一样的字符,比如 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]);
有时候,可能区间左端点访问不到末尾,从而得不到最优解,需要再动态规划一次,比如上面的例题。