折叠序列(区间dp&&输出具体方案)

比尔正在试图用折叠重复子序列的方式紧凑的表示由大写字母’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<

 

你可能感兴趣的:(dp)