比尔正在试图用折叠重复子序列的方式紧凑的表示由大写字母’A’到’Z’组成的字符序列。
例如,表示序列AAAAAAAAAABABABCCD的一种方式是10(A)2(BA)B2(C)D。
他通过以下方式定义了折叠的字符序列以及它们的展开变换:
1、包含带个字符的序列被认为是折叠序列,展开它得到的序列为它本身。
2、如果S和Q是两个折叠序列,并且S可以展开得到S’,Q可以展开得到Q’,则认为SQ也是一个折叠序列,并且SQ展开得到S’Q’。
3、如果S是折叠序列,则X(S)也是折叠序列,其中X为大于1的整数。如果S展开得到S’,则X(S)展开得到X个S’。
根据定义可以展开任意给出的折叠序列,现在给出原序列,请你将它折叠,并使得折叠序列包含尽可能少的字符数。
输入格式
输入包含一行由大写字母构成的字符序列,序列长度在1到100之间。
输出格式
输出包含字符数最少的折叠序列,如果答案不唯一则任意输出一个即可。
输入样例:
AAAAAAAAAABABABCCD
输出样例:
9(A)3(AB)CCD
思路:(区间dp)
这个题通过分析输出结果可知:
要知道每个折叠序列的表示(即:x(C) )的长度(该表示由三部分组成:x:循环结的个数x,C:循环结,占两个字符长度的()),我们可以预处理出每个数字占的字符长度,剩下的就是求出折叠序列的最短长度以及判断每个循环结。
求整个折叠序列的表示的最短长度可用区间dp来做:
状态表示 :用 f[l][r] 表示[l,r]这个区间的最短折叠序列长度
状态计算:
分为两类:1、[l,r]可由[l,k]和[k+1,r]合并得到:f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]) k从l到r-1
2、[l,r]是一段序列折叠的结果:设[l,r]可由[l,k]循环(r-l+1)/(k-l+1) 次得到,即:可表示为 [(r-l+1)/(k-l+1)]( s[l,k] )
f[l][r]=min(f[l][r],f[l][k]+lenc[(r-l+1)/(k-l+1)]+2); lenc[i]为数字i占的字符位数
如何判断循环结:
首先,既然[L,R]可由[l,r]循环得到,那么从整体长度上来看,len([L,R])能被len([l,r])整除,即:(R-L+1)%(r-l+1)==0
然后,就是对应位置的字符要对应相同 ,即:s[L]=s[l] s[L+1]=s[l+1]....s[L+k]=s[l+k%(r-l+1)]...s[R]=s[l+(R-L)%(r-l+1)]
求出f[1][n]后,可根据dp过程中每次判断最优解递归回去将折叠序列还原出来(即:根据每次的dp结果倒推);
完整代码:
#include
#include
#include
using namespace std;
const int maxn=110;
char s[maxn],lenc[maxn];
int f[maxn][maxn];
void init()//计算每个数字占的字符长度
{
for(int i=0;i<=100;i++){
if(i<10) lenc[i]=1;
else if(i>=10&&i<=99) lenc[i]=2;
else lenc[i]=3;
}
}
bool isSubStr(int l,int r,int L,int R)//判断[L,R]能否通过[l,r]循环得到
{
if((R-L+1)%(r-l+1)) return false;
for(int i=L;i<=R;i++){
if(s[i]!=s[l+(i-L)%(r-l+1)]) return false;
}
return true;
}
void show(int l,int r)
{
if(f[l][r]==r-l+1){
for(int i=l;i<=r;i++) cout<