题意:给你一个串,让你从左到右构造这个串,一次操作可以直接在当前串后面添加一个任意字符,或者拷贝当前串的任意一个子串到当前串的后面。问你最少要多少次操作才能构造出这个串。
从前向后贪心,从当前已构造的串的后面开始,尽量往后走,尝试在后缀自动机上转移,直到不能转移为止,便求出了后面的串的在当前串中能找到的最长前缀是多少,然后答案++,然后把这个最长前缀加入后缀自动机接着跑即可。不会证明。
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define MAXL 200000 #define MAXC 26 int v[2*MAXL+10],__next[2*MAXL+10],first[2*MAXL+10],e; void AddEdge(int U,int V){ v[++e]=V; __next[e]=first[U]; first[U]=e; } char s[MAXL+10]; int len; struct SAM{ int endcnt[2*MAXL+10]; int n,maxlen[2*MAXL+10],minlen[2*MAXL+10],trans[2*MAXL+10][MAXC],slink[2*MAXL+10]; void clear(){ for(int i=0;i<=n;++i){ endcnt[i]=maxlen[i]=minlen[i]=slink[i]=first[i]=0; memset(trans[i],0,sizeof(trans[i])); } n=e=0; } int new_state(int _maxlen,int _minlen,int _trans[],int _slink){ maxlen[n]=_maxlen; minlen[n]=_minlen; for(int i=0;i<MAXC;++i){ if(_trans==NULL){ trans[n][i]=-1; } else{ trans[n][i]=_trans[i]; } } slink[n]=_slink; return n++; } int add_char(char ch,int u){ if(u==-1){ return new_state(0,0,NULL,-1); } int c=ch-'a'; int z=new_state(maxlen[u]+1,-1,NULL,-1); endcnt[z]=1; int v=u; while(v!=-1 && trans[v][c]==-1){ trans[v][c]=z; v=slink[v]; } if(v==-1){ minlen[z]=1; slink[z]=0; return z; } int x=trans[v][c]; if(maxlen[v]+1==maxlen[x]){ minlen[z]=maxlen[x]+1; slink[z]=x; return z; } int y=new_state(maxlen[v]+1,-1,trans[x],slink[x]); slink[y]=slink[x]; minlen[x]=maxlen[y]+1; slink[x]=y; minlen[z]=maxlen[y]+1; slink[z]=y; int w=v; while(w!=-1 && trans[w][c]==x){ trans[w][c]=y; w=slink[w]; } minlen[y]=maxlen[slink[y]]+1; return z; } }sam; int ans; int main(){ // freopen("e.in","r",stdin); scanf("%s",s); len=strlen(s); int U=sam.add_char(0,-1); int i=0; while(i<len){ if(sam.trans[0][s[i]-'a']==-1){ U=sam.add_char(s[i],U); ++i; } else{ int j=i,V=0; while(V!=-1 && j<len){ V=sam.trans[V][s[j]-'a']; ++j; } if(j==len && V!=-1){ ++ans; break; } for(int k=i;k<j-1;++k){ U=sam.add_char(s[k],U); } i=j-1; } ++ans; } printf("%d ",ans); return 0; }