bzoj 1090(区间dp)

传送门

题意:

给你一串字符串,你现在你可以把一串相同的串压缩成一个串,现在问你压缩之后最小的字符串个数。

分析:

一道非常有意思的区间dp的问题。

这个问题本质上跟石子合差不多,都是可以把区间某个部分压缩合并,本质上的状态转移方程均为:\(dp[l][r]=\min(dp[l][r],dp[l][k]+dp[k+1][r])\);而只不过在这个问题中,我们合并压缩的过程中计算贡献的方法跟石子合并不一样。

我们考虑在这个问题中如何求解贡献。现在要使得一个长串\(str_1\)能够用一个小串\(str_2\)表示出来,那么,显然这就代表着串\(str_2\)必定是\(str_1\)的某一个循环节,如果满足这样的条件,设循环节长度为\(len\),则当前的区间\([l,r]\)就可以对答案贡献出\(2+Bit(\frac{r-l+1}{len})\)的贡献。而这一步的更新,我们可以在枚举断点\(k\)的过程中进行更新,因此,如果枚举的区间\([l,k]\)是区间\([l,r]\)的循环节,则会有状态转移:\(dp[l][r]=\min(dp[l][r],dp[l][k]+2+Bit(\frac{r-l+1}{len})\)

处理好贡献之后,之后就是最基本的区间dp向上更新的过程。因为我们还需要判断某个子串是否是循环节,因此整体的时间复杂度为:\(\mathcal{O}(n^4)\)

代码:

#include <bits/stdc++.h>
#define maxn 105
using namespace std;
int dp[maxn][maxn];
char str[maxn];
const int inf=0x3f3f3f3f;
int getbit(int x){
    int cnt=0;
    while(x){
        cnt++;
        x/=10;
    }
    return cnt;
}
bool judge(int l,int r,int L,int R){
    int len=(R-L+1),k=R;
    while(1){
        for(int i=L;i<=R;i++){
            k++;
            if(str[k]!=str[i]) return false;
        }
        if(k==r) return true;
    }
}
int main()
{
    scanf("%s",str+1);
    int n=strlen(str+1);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            dp[i][j]=inf;
    for(int i=1;i<=n;i++) dp[i][i]=1;
    for(int p=1;p<=n;p++){
        for(int i=1,j=p+1;j<=n&&i<=n;i++,j=i+p){
            for(int k=i;k<j;k++){
                if(judge(i,j,i,k)) dp[i][j]=min(dp[i][j],dp[i][k]+2+getbit((j-i+1)/(k-i+1)));
                dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);
            }
        }
    }
    printf("%d\n",dp[1][n]);
}
posted @ 2019-07-20 10:22  ChenJr  阅读(159)  评论(0编辑  收藏  举报