题意:
给出一个字符串,求这个字符串长度为1-n的子串的最大出现次数;
字符串长度<=250000;
题解:
几天没更新,水一发后缀自动机的题解吧;
首先定义后缀自动机的的right集合大小,就是该结点代表的状态能拓展到的子串种类;
那么对于长度为x的子串的答案就是所有长度>=x的结点的right值的最大值;
right值是不能再构建自动机时增量维护的,所以只能在构建这个后缀自动机之后再O(n)搞一遍;
具体就是从反向后缀树的叶子开始,定义每个关键节点的right初始都为1,然后顺着求出所有节点的子树和就可以了;
然后再用right[x]更新f[len[x]],扫一遍让f[i]=max(f[i],f[i+1]);
时间复杂度O(n);
然而后缀自动机的构建依然是基本靠背的代码。。
而且大概后缀自动机顺便建出来的反向后缀树比那个识别后缀的本体要有用得多。。真是233
代码:
#include<queue> #include<stdio.h> #include<string.h> #include<algorithm> #define N 260000 #define S 26 using namespace std; char str[N]; int f[N]; namespace SAM { int son[N<<1][S],len[N<<1],pre[N<<1]; int in[N<<1],right[N<<1]; int tot,last; queue<int>q; int newnode() { tot++; memset(son[tot],0,sizeof(int)*S); pre[tot]=len[tot]=0; return tot; } void init() { tot=0; last=newnode(); } void Insert(int x) { int p,np=newnode(); right[np]=1; len[np]=len[last]+1; for(p=last;p&&!son[p][x];p=pre[p]) son[p][x]=np; if(!p) pre[np]=1; else { int q=son[p][x]; if(len[q]==len[p]+1) pre[np]=q; else { int nq=newnode(); pre[nq]=pre[q]; len[nq]=len[p]+1; memcpy(son[nq],son[q],sizeof(int)*S); pre[np]=pre[q]=nq; for(;son[p][x]==q;p=pre[p]) son[p][x]=nq; } } last=np; } void Build() { int x,i; for(i=1;i<=tot;i++) { in[pre[i]]++; } for(i=1;i<=tot;i++) { if(!in[i]) q.push(i); } while(!q.empty()) { x=q.front(),q.pop(); in[pre[x]]--; right[pre[x]]+=right[x]; if(!in[pre[x]]) q.push(pre[x]); } } void calc(int n) { for(int i=1;i<=tot;i++) f[len[i]]=max(f[len[i]],right[i]); for(int i=n-1;i>=1;i--) f[i]=max(f[i],f[i+1]); } } int main() { int n,m,i,j,k; scanf("%s",str+1); n=strlen(str+1); SAM::init(); for(i=1;i<=n;i++) { SAM::Insert(str[i]-'a'); } SAM::Build(); SAM::calc(n); for(i=1;i<=n;i++) { printf("%d\n",f[i]); } return 0; }