1. 程式人生 > >bzoj1090字串摺疊

bzoj1090字串摺疊

Description
摺疊的定義如下: 1. 一個字串可以看成它自身的摺疊。記作S  S 2. X(S)是X(X>1)個S連線在一起的串的摺疊。記作X(S)  SSSS…S(X個S)。 3. 如果A  A’, BB’,則AB  A’B’ 例如,因為3(A) = AAA, 2(B) = BB,所以3(A)C2(B)  AAACBB,而2(3(A)C)2(B)AAACAAACBB 給一個字串,求它的最短折疊。例如AAAAAAAAAABABABCCD的最短折疊為:9(A)3(AB)CCD。
Input
僅一行,即字串S,長度保證不超過100。
Output
僅一行,即最短的摺疊長度。
Sample Input
NEERCYESYESYESNEERCYESYESYES
Sample Output
14
HINT
一個最短的摺疊為:2(NEERC3(YES))

沒想到居然是dp,區間dp
定義dp[i][j]表示字串[i,j]最小摺疊長度,有兩種狀態轉移的形式,如果l到r這個區間裡面不能摺疊,那就只能列舉中間點,由左右兩部分可摺疊長度相加的轉移而來
f ( l , r ) =

m i n ( f ( l , r ) ,
f ( l , i ) + f ( i + 1 , r ) ) f(l,r)=min(f(l,r),f(l,i)+f(i+1,r))
而如果本身這個區間就可以摺疊的,那假設右邊部分可以由左邊部分重複而來,那麼就是
f ( l , r ) = m i n ( f ( l , r ) , f ( l , i ) + 2 + c a l ( ( r i ) / ( i l + 1 ) + 1 ) ) f(l,r)=min(f(l,r),f(l,i)+2+cal((r-i)/(i-l+1)+1))
這個f(l,i)就是左邊部分的長度,摺疊後就以這個來表示,2就是括號長度,後面的cal就是計算整個l,r區間由多少個左部分組成,就是這個數字的長度,比如14,長度就是2

程式碼:

#include <bits/stdc++.h>
using namespace std;
const int N=105;
char s[N];
//dp[i][j]表示[i,j]最小摺疊長度
int dp[N][N];
int cal(int x){
    int t=0;
    while(x){
        t++;
        x/=10;
    }
    return t;
}
//判斷[l,r]能否由[cl,cr]重複而成
bool judge(int l,int r,int cl,int cr){
    //長度不能整除
    if((r-l+1)%(cr-cl+1)!=0){
        return false;
    }
    //列舉右部分每一個字元
    for(int i=l;i<=r;i++){
        //按順序判斷,注意取模
        //右邊部分可能有多個左邊部分
        if(s[i]!=s[(i-l)%(cr-cl+1)+cl]){
            return false;
        }
    }
    return true;
}
int solve(int l,int r){
    if(l==r){
        return 1;
    }
    if(dp[l][r]){
        return dp[l][r];
    }
    int t=r-l+1;
    //列舉斷點
    for(int i=l;i<r;i++){
        t=min(t,solve(l,i)+solve(i+1,r));
        if(judge(i+1,r,l,i)){
            //+solve(l,i)摺疊後那部分的長度
            //+2兩個括號
            //+get()摺疊後數字的長度
            t=min(t,solve(l,i)+2+cal((r-i)/(i-l+1)+1));
        }
    }
    return dp[l][r]=t;
}
int main(void){
    scanf("%s",s);
    int len=strlen(s);
    printf("%d\n",solve(0,len-1));
    return 0;
}