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]);
}