题目链接
http://www.lydsy.com/JudgeOnline/problem.php?id=1068
思路
不妨设整个字符串长度为
n
。根据题意,我们可以看作在区间
实际上我们只需要DP出字符串中怎么放M就可以了,如果发现当前dp的区间中不含M,并且左右两半是一样的,那么就在中点处放一个R就行了。因此我们用
f[L][R][k]
来表示区间
[L,R]
,
k=1
表示
[L,R]
中有缝隙处放了M(注:不包含
L−1和R+1
,并且约定
L−1
处一定有个M,或
L−1=0
),压缩后的字符串长度。显然边界条件是
f[i][i][0]=1,f[i][i][1]=inf
(因为只有一个元素的区间就没有缝隙放M)。那么对于区间
[L,R]
,如果其中放了M,那么我们就暴力枚举其中一个M放在哪里,并用分割后的两个区间的状态做转移。如果其中没放M,我们先判断这个区间是否是一个循环节(左右两半的字符串一样),是的话那么这个区间只需要左半部分压缩后的长度+一个R就够了。否则我们就枚举R放在哪里,这样这个R就和前面的
L−1和L
之间的那个M对应起来,括起来了一个循环节部分,然后再用这个循环节部分的dp值做转移,R后面的那部分保持压缩之前的样子不变。
说起来比较复杂,不过看代码就很清晰了。注意那个判断循环节的函数,一定要有特判,即这个区间长度是偶数,如果是奇数显然不能把这段字符串分成相等的两半,不特判会WA!!!
代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#define MAXN 55
#define INF 0x3f3f3f3f
using namespace std;
int f[MAXN][MAXN][2];
char s[MAXN];
int n;
bool isSame(int L,int R) //判断区间[L,M],[M+1,R]是否相同
{
if((R-L+1)&1) return false; //注意特判!!!!!!!!!!
int M=(L+R)>>1;
for(int i=L;i<=M;i++)
{
int j=M+i-L+1;
if(s[i]!=s[j])
return false;
}
return true;
}
int dp(int L,int R,bool k) //f[L][R][k]
{
if(f[L][R][k]<INF) return f[L][R][k];
int len=R-L+1; //len=[L,R]区间长度
if(len==1)
{
if(k) return f[L][R][k]=INF; //不合法
else return f[L][R][k]=1;
}
int M=(L+R)>>1;
if(k) //放了M,那么暴力枚举这个M放置的位置
{
for(int i=L;i<R;i++)
{
f[L][R][k]=min(f[L][R][k],dp(L,i,1)+1+dp(i+1,R,1));
f[L][R][k]=min(f[L][R][k],dp(L,i,0)+1+dp(i+1,R,1));
f[L][R][k]=min(f[L][R][k],dp(L,i,1)+1+dp(i+1,R,0));
f[L][R][k]=min(f[L][R][k],dp(L,i,0)+1+dp(i+1,R,0));
}
return f[L][R][k];
}
if(isSame(L,R)) //如果[L,R]区间是两个相同的字符串拼起来的话,那么这个区间只需要左边一半加上一个R的长度来表达
f[L][R][k]=dp(L,M,0)+1;
for(int i=L;i<R;i++)
f[L][R][k]=min(f[L][R][k],dp(L,i,0)+R-i);
return f[L][R][k];
}
int main()
{
memset(f,INF,sizeof(f));
scanf("%s",s+1);
n=strlen(s+1);
printf("%d\n",min(dp(1,n,0),dp(1,n,1)));
return 0;
}