前言
最近刷DP已经较有感觉了…
题目
给出一个由大写字母组成的长度为n(1<=n<=100)的串“折叠”成一个尽量短的串,例如 AAAAAAAAAABABABCCD折叠成 9(A)3(AB)CCD ,注意,数字和括号都要算入长度,折叠是可以嵌套的,例如NEERCYESYESYESNEERCYESYESYES可以折叠成2(NEERC3(YES)),多解释输出任意一组解.注意多组数据输入.
in
i
n
AAAAAAAAAABABABCCD
NEERCYESYESYESNEERCYESYESYES
out
o
u
t
9(A)3(AB)CCD
2(NEERC3(YES))
思路
这是一道区间DP,看出来还是很容易的
我们令
f[i][j]
f
[
i
]
[
j
]
表示区间[i,j]折叠操作(也可以不折叠)后的最小长度字符串
知道这点后,那我们怎么做呢?
①自身折叠
我们可以在原串[i,j]中枚举表示我们要以i开始长度为k的子串为折叠后的字符串,然后如果原串都是由[i,k]的子串连续组成的,那就可以折叠,那我们显然可以发现,j-i+1必须为k-i+1的倍数,注意,我们的k要从小往大枚举,因为这样后只要找到一组可行的方案就可以返回(作者在这里写了一个Fold函数),那就意味着第一个状态转移方程式:
f[i][j]=min(f[i][j],′num′+′(′+f[i][i+k−1]+′)′)(1<=k<=l/2andFold==true)
f
[
i
]
[
j
]
=
m
i
n
(
f
[
i
]
[
j
]
,
′
n
u
m
′
+
′
(
′
+
f
[
i
]
[
i
+
k
−
1
]
+
′
)
′
)
(
1
<=
k
<=
l
/
2
a
n
d
F
o
l
d
==
t
r
u
e
)
注意,这里min指的是比较长度
②拼接而成
在这里我们并不需要将一个串真的枚举拆分位置而拆成多个子串拼接,因为这是一个DP的过程,你每次只需要确定一个拆分点然后取答案最优值就可以了.
那么我们还是在[i,j]中枚举一个k,但这里是拆分点而不是长度,那么
f[i][j]=min(f[i][j],f[i][k]+f[k+1][j])(i<=k<j)
f
[
i
]
[
j
]
=
m
i
n
(
f
[
i
]
[
j
]
,
f
[
i
]
[
k
]
+
f
[
k
+
1
]
[
j
]
)
(
i
<=
k
<
j
)
注意,这里min指的是比较长度
那么最后的答案就是
f[0][n−1]
f
[
0
]
[
n
−
1
]
(n为a的长度)
代码
#include<set>
#include<map>
#include<ctime>
#include<queue>
#include<cmath>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
#define MAXN 100
#define INF 0x3f3f3f3f
string str,f[MAXN+5][MAXN+5];
int Check(int i,int j,int l){
for(int k=1;k<=l/2;k++){//枚举要折叠的长度
if(l%k) continue;//必须整除才能折叠
int flag=0,tmp=0;
for(int x=i+k;x<=j;x++,tmp=(tmp+1)%k)
if(str[x]!=str[i+tmp]){//判断是否每段相等
flag=1;
break;
}
if(!flag)
return k;
}//无法折叠
return -1;
}
int main(){//f[i][j]:[i,j]最短折叠长度
string chan;
char chan1[55];
while(cin>>str){
int n=str.size();
for(int i=0;i<n;i++)
f[i][i]=str[i];
for(int l=2;l<=n;l++)
for(int i=0;i<n;i++){
int j=i+l-1;if(j>=n) continue;
f[i][j]=str.substr(i,l);//截取原串
int klen=Check(i,j,l);
if(klen!=-1){
int cnt=l/klen;//获得折叠后串前的数字
sprintf(chan1,"%d",cnt);//转换
chan=string(chan1);
if(klen+int(chan.size())+2<l)
f[i][j]=chan+"("+f[i][i+klen-1]+")";
}
int p=-1,siz=int(f[i][j].size());
for(int k=i;k<j;k++){//枚举分割点
if(siz>int(f[i][k].size()+f[k+1][j].size()))
siz=int(f[i][k].size()+f[k+1][j].size()),p=k;
}
if(p!=-1)//进行合并
f[i][j]=f[i][p]+f[p+1][j];
}//输出答案
cout<<f[0][n-1]<<endl;
}
return 0;
}