【SAM】bzoj5084: hashit
做得心 力 憔 悴
Description
你有一个字符串S,一开始为空串,要求支持两种操作
在S后面加入字母C
删除S最后一个字母
问每次操作后S有多少个两两不同的连续子串
Input
一行一个字符串Q,表示对S的操作
如果第i个字母是小写字母c,表示第一种加字母c的操作
如果为-表示删除操作,保证所有删除操作前S都非空
|Q|<=10^5
Output
输出|Q|行,第i行表示i个操作之后S内有多少个不同子串
题目分析
陈老师神题x2,暂时只会做法一。
做法一:暴力回退
还是自己菜啊……这么个暴力都写了老久。
思路就是将每一次的修改值都记录下来,再在$undo()$里回退每一个修改。说得是轻松,但是具体的实现还是要仔细安排一下顺序和细节的。
其中$stk[top]$存修改的起止点;$last[]$存每次$extend()$前的$lst$值,用于路径回退上的$ch[x][c]$修改;$tag[]$表示点$i$这次操作新增点的数量,需要分为1,2讨论回退;$val[]$表示每次操作插入的字符$c$.
有2个新增点的$undo()$稍微复杂一点,这里$stk[]={从lst回退的p';fa[q];再次回退的p'';q}$.
1 #include<bits/stdc++.h> 2 const int maxn = 200035; 3 4 int n,top,stk[maxn<<2],last[maxn<<2],tag[maxn<<2],val[maxn<<2]; 5 long long ans; 6 struct SAM 7 { 8 int ch[maxn][27],fa[maxn],len[maxn],lst,tot; 9 void init() 10 { 11 lst = tot = 1; 12 } 13 void extend(int c) 14 { 15 int p = lst, np = ++tot; 16 last[++last[0]] = lst; 17 lst = np, len[np] = len[p]+1; 18 val[tot] = c; 19 for (; p&&!ch[p][c]; p=fa[p]) ch[p][c] = np; 20 tag[tot] = 0, stk[++top] = p; 21 if (!p) fa[np] = 1; 22 else{ 23 int q = ch[p][c]; 24 if (len[p]+1==len[q]) fa[np] = q; 25 else{ 26 int nq = ++tot; 27 len[nq] = len[p]+1; 28 tag[tot] = 2, val[tot] = c; 29 memcpy(ch[nq], ch[q], sizeof ch[q]); 30 stk[++top] = fa[q]; 31 fa[nq] = fa[q], fa[q] = fa[np] = nq; 32 for (; p&&ch[p][c]==q; p=fa[p]) ch[p][c] = nq; 33 stk[++top] = p, stk[++top] = q; 34 } 35 } 36 ans += len[np]-len[fa[np]]; 37 if (!tag[tot]) tag[tot] = 1; 38 } 39 void undo() 40 { 41 if (tag[tot]==1){ 42 int p = last[last[0]--], fnd = stk[top--], c = val[tot]; 43 ans -= len[tot]-len[fa[tot]], lst = p; 44 for (; p!=fnd; p=fa[p]) ch[p][c] = 0; 45 memset(ch[tot], 0, sizeof ch[tot]), --tot; 46 }else{ 47 int q = stk[top--], fnd2 = stk[top--], fatq = stk[top--], fnd1 = stk[top--]; 48 int p = last[last[0]--], c = val[tot]; 49 ans -= len[tot-1]-len[fa[tot-1]], lst = p; 50 for (; p!=fnd1; p=fa[p]) ch[p][c] = 0; 51 fa[q] = fatq; 52 for (; p!=fnd2; p=fa[p]) ch[p][c] = q; 53 memset(ch[tot], 0, sizeof ch[tot]), --tot; 54 memset(ch[tot], 0, sizeof ch[tot]), --tot; 55 } 56 } 57 }f; 58 char s[maxn]; 59 60 int main() 61 { 62 f.init(); 63 scanf("%s",s+1); 64 n = strlen(s+1); 65 for (int i=1; i<=n; i++) 66 { 67 if (s[i]=='-') f.undo(); 68 else f.extend(s[i]-'a'); 69 printf("%lld\n",ans); 70 } 71 return 0; 72 }
做法二:后缀平衡树
具体的实现似乎可以用hash求LCP+multiset解决
END