链接:http://acm.hdu.edu.cn/showproblem.php?pid=4641
题意:有一个长度不超过5e4的字符串,Q(Q<=2e5)次操作;
操作分为:1 ch 想字符串末尾插入一个字符ch; 2 表示查询目前的字符串中子串出现次数不小于k次的不同子串数目;
思路:SAM在线求解;
对于每次找到将一个字符x插入到SAM之后,我们知道pre[p]所含有的Tx的后缀字符串数目为step[pre[np]]个,那么只需要每次插入之后更新下这些字符串出现的次数cnt即可;
由于Right(fa)与Right(r)没有交集(max(fa) = min(r) - 1),所以需要一直递推到root,但是root不能计算,因为root并没有表示后缀,只是一个init状态;
还有一点就是在拷贝q的信息到nq中时,主要把cnt的信息也拷贝过去;
#include<iostream> #include<cstdio> #include<cstring> using namespace std; #define maxn 100007 #define SIGMA_SIZE 26 struct SAM{ int sz,tot,last,k; int g[maxn<<1][SIGMA_SIZE],pre[maxn<<1],step[maxn<<1]; int vs[maxn<<1],cnt[maxn<<1]; void newNode(int s){ step[++sz] = s; pre[sz] = 0; vs[sz] = cnt[sz] = 0; memset(g[sz],0,sizeof(g[sz])); } void init(){ tot = 0; sz = 0; last = 1; newNode(0); } int idx(char ch){return ch - ‘a‘;} void Insert(char ch){ newNode(step[last]+1); int v = idx(ch), p = last, np = sz; while(p && !g[p][v]) g[p][v] = np,p = pre[p]; //知道找到Right集合中包含x的边的祖宗节点 if(p){ int q = g[p][v]; if(step[q] == step[p] + 1) pre[np] = q; else{ newNode(step[p]+1); int nq = sz; //nq替换掉q节点 for(int i = 0;i < SIGMA_SIZE;i++) g[nq][i] = g[q][i]; cnt[nq] = cnt[q]; //** pre[nq] = pre[q]; pre[np] = pre[q] = nq; while(p && g[p][v] == q) g[p][v] = nq,p = pre[p]; } } else pre[np] = 1; for(int aux = np;aux != 1 && !vs[aux];aux = pre[aux]){ if(++cnt[aux] >= k){ tot += step[aux] - step[pre[aux]]; vs[aux] = true; //该父节点的子串已经加到tot中 } } last = np; } }SA; char str[maxn]; int main() { int n,Q; while(scanf("%d%d%d",&n,&Q,&SA.k) == 3){ scanf("%s",str); SA.init(); int len = strlen(str); for(int i = 0;i < len;i++){ SA.Insert(str[i]); } int op; char ch[2]; while(Q--){ scanf("%d",&op); if(op & 1){ scanf("%s",ch); SA.Insert(ch[0]); } else printf("%d\n",SA.tot); } } }
时间: 2024-12-18 08:21:51