Jzoj3443 压缩
一个文本压缩后由若干个单元组成,每个单元由3部分组成:1.正文(小写字母组成的字符串)2.若干个”*”,表示正文的又重复了几次3.单元的结尾符号”$”,显然,对于同一文本,压缩后的表示方法不唯一,但是为了方便,我们要求你采用压缩后字符串最短的压缩方法,如果有多种压缩方法,只需输出任意一种。n<=2000
看到数据范围我们发现可以用dp,设f[i][j]表示目前在第i位,最后一个循环串的长度为j的情况下,最短的长度
那么显然,无论怎么样,有一种转移总是成立的,f[i][j]=min(f[i-j][k]+j+1),这相当于新开了一个循环节
而另一个转移需要满足一个条件,f[i][j]=min(f[i-j][j]+1),当s(i-j+1,i)=s(i-j*2+1,i-j)时,这相当于在循环节里面加入一个*
那么综合下来转移就是n^3的,我们加上两个优化
1.令g[i]为min(f[i][k]),避免每次枚举k
2.判断字串相等用hash
这样我们可以将复杂度将为n^2
接下来说一下如何输出方案
先记录下s[i][j]表示状态i,j的最后一个循环节j循环次数,和上面两种转移对应,s[i][j]=1或者s[i][j]=s[i][i-j]+1
我们令d[i]表示答案为g[i]时,最后一个循环节的循环次数,令r[i]为此循环节的长度,那么求解g[i]时,就可以顺便求解d[i]和r[i]
最后输出即可
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define L long long
using namespace std;
int n,m=0; char str[2010],A[2010];
int f[2010][2010],b[2010],d[2010];
int h[2010],g[2010],s[2010][2010],r[2010];
__attribute__((optimize("-O3")))
inline L gH(int l,int r){ return h[r]-h[l-1]*b[r-l+1]; }
int main(){
freopen("compress.in","r",stdin);
freopen("compress.out","w",stdout);
scanf("%s",str+1); n=strlen(str+1);
for(int i=*b=1;i<=n;++i){
b[i]=b[i-1]*27;
h[i]=h[i-1]*27+str[i]-'a';
}
memset(g,127,sizeof g); *g=0;
for(int i=1;i<=n;++i)
for(int j=1;j<=i;++j){
f[i][j]=g[i-j]+j+1; s[i][j]=1;
if(f[i][j]<g[i]){ d[i]=1; r[i]=j; g[i]=f[i][j]; }
if(i>=(j<<1) && gH(i-j+1,i)==gH(i-j*2+1,i-j))
if(f[i][j]>f[i-j][j]+1){
s[i][j]=s[i-j][j]+1;
f[i][j]=f[i-j][j]+1;
if(f[i][j]<g[i])
{ d[i]=s[i][j]; r[i]=j; g[i]=f[i][j]; }
}
g[i]=min(g[i],f[i][j]);
}
for(;n;){
A[m++]='$';
for(int i=1;i<d[n];++i) A[m++]='*';
for(int i=n;i>n-r[n];A[m++]=str[i--]);
n-=r[n]*d[n];
}
reverse(A,A+m); puts(A);
}