题意:
求一个串分成k个回文串需要的最少操作数,这个操作是指将串的某个位置的值改变以使得分成的串能够成为回文。
题解:
感觉神题啊,路径打印十分困难。
第一、我们要预处理下区间[i,j]上串变成回文的操作数!这步要用到区间dp来预处理,不断划分区间跟新。
第二、dp计算出最小的操作数,dp[i][j]表示前j个点分成i份回文串需要的最小操作数。
第三、获取,这就需要第二步中dp的值来倒推得到路径 通过这个判断:
if(dp[i][later]==dp[i-1][j]+cost[j+1][later]) 不断跟新later倒着推防止后效性!
路径的标记用mark[i]表示第i个切点的位置的后一个位置。
这还没完,有可能分割会出现相同的情况因此要盘重!! if(mark[i-1]!=mark[i]) mark[cnt++]=mark[i]
这些弄完后就可以开始修改字符串,用词要输出修改后的字符串,这步比较有技巧性,具体见代码!
第四、打印路径,路径就简单了,直接通过mark[i]标记的位置,然后在对应位置输出'+'
真心是神题!难点主要在第三部路径的获取处理,简直无解!
#include<iostream> #include<math.h> #include<stdio.h> #include<algorithm> #include<string.h> #include<vector> #include<map> using namespace std; typedef long long lld; const int oo=0x3f3f3f3f; const lld OO=1LL<<61; const int MOD=1000000007; #define maxn 505 int dp[maxn][maxn]; int cost[maxn][maxn]; int mark[maxn]; char str[maxn]; int n,k; void TheCnt() { n=strlen(str+1); memset(cost,0,sizeof cost); for(int j=1;j<=n;j++) for(int i=1;i<=j;i++) cost[i][j]=cost[i+1][j-1]+(str[i]!=str[j]); } void Dp() { memset(dp,0x3f,sizeof dp); dp[0][0]=0; for(int i=1;i<=k;i++) { for(int j=i;j<=n;j++) { for(int jj=i-1;jj<=j;jj++) if(dp[i-1][jj]!=oo) { dp[i][j]=min(dp[i-1][jj]+cost[jj+1][j],dp[i][j]); } } } printf("%d\n",dp[k][n]); } void output() { int cnt; int later=n; for(int i=k;i>=1;i--) { for(int j=1;j<=n;j++) { if(dp[i-1][j]!=oo&&dp[i][later]==dp[i-1][j]+cost[j+1][later]) { mark[i]=j+1; later=j; break; } } } mark[k+1]=n+1; mark[1]=1; cnt=1; ///防止有重复的切点 ///处理: for(int i=1;i<=k+1;i++) if(mark[i-1]!=mark[i]) mark[cnt++]=mark[i]; cnt-=2;//分成了cnt+1段 mark[cnt+1]=n+1; ///修改字符串: for(int i=1;i<=cnt;i++) { int u=mark[i],v=mark[i+1]-1; while(u<v) { if(str[u]!=str[v]) str[v]=str[u]; u++; v--; } } cnt=2;//第一个的前面不用加+号所以直接跳过!! for(int i=1;i<=n;i++) { printf("%c",str[i]); if(i==mark[cnt]-1) { if(mark[cnt]!=n+1)printf("+"); cnt++; } } puts(""); } int main() { while(scanf("%s%d",str+1,&k)!=EOF) { TheCnt(); Dp(); output(); } return 0; } /** abacaba 1 abdcaba 2 abdcaba 5 abacababababbcbabcd 3 */