题意:
给你一串字符串,你现在你可以把一串相同的串压缩成一个串,现在问你压缩之后最小的字符串个数。
分析:
一道非常有意思的区间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
",dp[1][n]);
}