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