BZOJ3881[Coci2015]Divljak——AC自动机+树状数组+LCA+dfs序+树链的并
题目描述
Alice有n个字符串S_1,S_2...S_n,Bob有一个字符串集合T,一开始集合是空的。
接下来会发生q个操作,操作有两种形式:
“1 P”,Bob往自己的集合里添加了一个字符串P。
“2 x”,Alice询问Bob,集合T中有多少个字符串包含串S_x。(我们称串A包含串B,当且仅当B是A的子串)
Bob遇到了困难,需要你的帮助。
输入
第1行,一个数n;
接下来n行,每行一个字符串表示S_i;
下一行,一个数q;
接下来q行,每行一个操作,格式见题目描述。
输出
对于每一个Alice的询问,帮Bob输出答案。
样例输入
3
a
bc
abc
5
1 abca
2 1
1 bca
2 2
2 3
a
bc
abc
5
1 abca
2 1
1 bca
2 2
2 3
样例输出
1
2
1
2
1
提示
【数据范围】
1 <= n,q <= 100000;
Alice和Bob拥有的字符串长度之和各自都不会超过 2000000;
字符串都由小写英文字母组成。
这道题和bzoj2434都是很好的AC自动机练习题,都利用了fail树的性质来解决字符串问题。首先要知道一点:如果x串是y串的子串,那x串一定是y串一个前缀的后缀。我们知道AC自动机上每个点表示这个点到根节点的字符串。这道题的大体思路是把S集合建成AC自动机,每添加一个T集合中的字符串P就把他在AC自动机上跑一遍,把所有遍历的点及它们fail指针能达到的所有点的答案数都加1(遍历的点自然是这个串的子串,fail指针能达到的点都是遍历的点的后缀,当然也是加入的串的子串),然后查询。利用fail树的性质可以发现,每次插入一个串P,除了遍历的点,其他答案要加1的点都是这些点在fail树上的祖先,因此只要用树上差分将遍历的点+1就把所有答案要加的点都加了。但这样有的点就会被多加好几次,每次插入却最多只能把每个点加一次,因此,要把遍历的点按dfs序排序,然后将相邻两个点的lca到根节点答案都-1(也就是用差分将lca-1)就去重了(因为按dfs序相邻两个点最近,lca也最深)。查询时用树状数组在dfs序上区间求和就行了。
最后附上代码。
#include<cmath> #include<queue> #include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> using namespace std; int n,m; int sum; int num; int cnt; int tot; int opt; int g[100010]; int k[2000010]; int v[2000010]; int l[2000010]; int r[2000010]; int d[2000010]; char s[2000010]; int to[2000010]; int head[2000010]; int next[2000010]; int fail[2000010]; int f[23][2000010]; int a[2000010][26]; void add(int x,int y) { tot++; next[tot]=head[x]; head[x]=tot; to[tot]=y; } void change(int x,int val) { for(int i=x;i<=num;i+=i&-i) { v[i]+=val; } } int ask(int x) { int res=0; for(int i=x;i;i-=i&-i) { res+=v[i]; } return res; } void build(char *s,int c) { int now=0; int len=strlen(s); for(int i=0;i<len;i++) { int x=s[i]-'a'; if(!a[now][x]) { a[now][x]=++cnt; } now=a[now][x]; } g[c]=now; } void getfail() { queue<int>q; for(int i=0;i<26;i++) { if(a[0][i]) { q.push(a[0][i]); fail[a[0][i]]=0; } } while(!q.empty()) { int now=q.front(); q.pop(); add(fail[now],now); f[0][now]=fail[now]; for(int i=0;i<26;i++) { if(a[now][i]) { fail[a[now][i]]=a[fail[now]][i]; q.push(a[now][i]); } else { a[now][i]=a[fail[now]][i]; } } } } bool cmp(int x,int y) { return l[x]<l[y]; } void dfs(int x) { l[x]=++num; d[x]=d[f[0][x]]+1; for(int i=1;i<=21;i++) { f[i][x]=f[i-1][f[i-1][x]]; } for(int i=head[x];i;i=next[i]) { dfs(to[i]); } r[x]=num; } int lca(int x,int y) { if(d[x]<d[y]) { swap(x,y); } int dep=d[x]-d[y]; for(int i=0;i<=21;i++) { if(((1<<i)&dep)!=0) { x=f[i][x]; } } if(x==y) { return x; } for(int i=21;i>=0;i--) { if(f[i][x]!=f[i][y]) { x=f[i][x]; y=f[i][y]; } } return f[0][x]; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%s",s); build(s,i); } getfail(); dfs(0); scanf("%d",&m); for(int i=1;i<=m;i++) { scanf("%d",&opt); if(opt==1) { scanf("%s",s); int len=strlen(s); int now=0; int x=0; for(int j=0;j<len;j++) { now=a[now][s[j]-'a']; k[++x]=now; } sort(k+1,k+1+x,cmp); for(int j=1;j<=x;j++) { change(l[k[j]],1); } for(int j=1;j<x;j++) { change(l[lca(k[j],k[j+1])],-1); } } else { scanf("%d",&sum); printf("%d\n",ask(r[g[sum]])-ask(l[g[sum]]-1)); } } }