CF547E Milk and Friends(AC自动机的fail指针上建主席树 或 广义后缀自动机的parent线段树合并)
What-The-Fatherland is a strange country! All phone numbers there are strings consisting of lowercase English letters. What is double strange that a phone number can be associated with several bears!
In that country there is a rock band called CF consisting of n bears (including Mike) numbered from 1 to n.
Phone number of i-th member of CF is si. May 17th is a holiday named Phone Calls day. In the last Phone Calls day, everyone called all the numbers that are substrings of his/her number (one may call some number several times). In particular, everyone called himself (that was really strange country).
Denote as call(i, j) the number of times that i-th member of CF called the j-th member of CF.
The geek Mike has q questions that he wants to ask you. In each question he gives you numbers l, r and k and you should tell him the number
Input
The first line of input contains integers n and q (1 ≤ n ≤ 2 × 105 and 1 ≤ q ≤ 5 × 105).
The next n lines contain the phone numbers, i-th line contains a string si consisting of lowercase English letters ().
The next q lines contain the information about the questions, each of them contains integers l, r and k (1 ≤ l ≤ r ≤ n and 1 ≤ k ≤ n).
Output
Print the answer for each question in a separate line.
Examples
5 5
a
ab
abab
ababab
b
1 5 1
3 5 1
1 5 2
1 5 3
1 4 5
7
5
6
3
6
题意:题目给你N个字符串,Q组询问(1<=N , Q<=200000).(∑s[i] <=200000 )
题解一(后缀自动机):
我们将这N个字符串建立SAM,然后将其parent tree拉出来,那么对于一个节点所表示的单词,他出现的次数就是他的子树的大小,由于题目是要求l~r之间,所以是他的子树中endpos在一定范围内的点的数量。我们建立SAM时,对于第k个字符串,每加入一个节点,就在对应节点的线段树里面的第k个位置加一。 我们用线段树维护每个节点的子树,然后线段树合并。
对于每一个询问,我们只要在对应的子树里面找l~r区间的和就行了。
参考代码:
#include<bits/stdc++.h> using namespace std; typedef long long ll; #define pii pair<int,int> #define pil pair<int,ll> #define mkp make_apir const int INF=0x3f3f3f3f; const int maxn=2e5+10; int N,Q; char s[maxn]; int nxt[maxn<<1][26],l[maxn<<1],fa[maxn<<1]; int last,tot,cnt[maxn<<1],c[maxn<<1]; int sz,p[maxn],T[maxn<<1]; struct Tr{ int ls,rs; int num; } tr[maxn*40]; void Update(int &x,int l,int r,int pos) { if(!x) x=++sz; tr[x].num++; if(l==r) return ; int mid=l+r>>1; if(pos<=mid) Update(tr[x].ls,l,mid,pos); else Update(tr[x].rs,mid+1,r,pos); } int Query(int x,int l,int r,int L,int R) { if(!x) return 0; if(L<=l&&r<=R) return tr[x].num; int mid=l+r>>1,res=0; if(L<=mid) res+=Query(tr[x].ls,l,mid,L,R); if(R>mid) res+=Query(tr[x].rs,mid+1,r,L,R); return res; } int Merge(int x,int y) { if(!x||!y) return x+y; int z=++sz; tr[z].ls=Merge(tr[x].ls,tr[y].ls); tr[z].rs=Merge(tr[x].rs,tr[y].rs); tr[z].num=tr[x].num+tr[y].num; return z; } void Mer() { for(int i=1;i<=tot;++i) cnt[l[i]]++; for(int i=1;i<=tot;++i) cnt[i]+=cnt[i-1]; for(int i=1;i<=tot;++i) c[cnt[l[i]]--]=i; for(int i=tot,x;i>1;--i) x=c[i],T[fa[x]]=Merge(T[x],T[fa[x]]); } void Init() { last=tot=1; sz=0; memset(nxt[tot],0,sizeof nxt[tot]); l[tot]=fa[tot]=0; } int NewNode() { ++tot; memset(nxt[tot],0,sizeof nxt[tot]); l[tot]=fa[tot]=0; return tot; } void Insert(int ch,int x) { int p,q,np,nq; if(nxt[last][ch]) { p=last;q=nxt[p][ch]; if(l[q]==l[p]+1) last=q; else { nq=NewNode(); l[nq]=l[p]+1; memcpy(nxt[nq],nxt[q],sizeof(nxt[q])); fa[nq]=fa[q];fa[q]=nq; while(p&&nxt[p][ch]==q) nxt[p][ch]=nq,p=fa[p]; last=nq; } } else { np=NewNode(),p=last; last=np; l[np]=l[p]+1; while(p&&!nxt[p][ch]) nxt[p][ch]=np,p=fa[p]; if(!p) fa[np]=1; else { q=nxt[p][ch]; if(l[q]==l[p]+1) fa[np]=q; else { nq=NewNode(); memcpy(nxt[nq],nxt[q],sizeof nxt[q]); fa[nq]=fa[q]; l[nq]=l[p]+1; fa[q]=fa[np]=nq; while(p&&nxt[p][ch]==q) nxt[p][ch]=nq,p=fa[p]; } } } } int main() { scanf("%d%d",&N,&Q); Init(); for(int i=1;i<=N;++i) { scanf("%s",s); int len=strlen(s); last=1; for(int j=0;j<len;++j) Insert(s[j]-'a',i),Update(T[last],1,N,i); p[i]=last; } Mer(); while(Q--) { int l,r,x; scanf("%d%d%d",&l,&r,&x); printf("%d\n",Query(T[p[x]],1,N,l,r)); } return 0; }
题解二(AC自动机):首先对于多串匹配问题,我们很容易想到AC自动机。在AC自动机的fail树中的每个节点,我们知道他出现的次数就是他的子树的大小。但是这题是要询问在第 l个 字符串 到 第r个字符串这(r-l+1)个字符串中,第x个字符串出现的次数,这我们可以想到主席树的询问历史版本信息的性质,那么我们就可以用主席树去维护AC自动机的fail指针的dfs序。然后我们只要求出第R棵树和地(L-1)棵树的差值就行了。
参考代码:
#include<bits/stdc++.h> using namespace std; typedef long long ll; #define pii pair<int,int> #define pil pair<int,ll> #define mkp make_apir const int INF=0x3f3f3f3f; const int maxn=2e5+10; int N,Q; char s[maxn]; int sz,ch[maxn][26],fail[maxn],val[maxn]; int in[maxn],out[maxn],tim,Len[maxn],tot,T[maxn]; vector<int> vec[maxn],ts[maxn]; void Init() { sz=1; tim=tot=0; memset(ch[sz],0,sizeof ch[sz]); memset(val,0,sizeof val); } int idx(char c){return c-'a';} void Insert(char *s,int x) { Len[x]=strlen(s);int u=0; for(int i=0;i<Len[x];++i) { int c=idx(s[i]); ts[x].push_back(c); if(!ch[u][c]){memset(ch[sz],0,sizeof ch[sz]);ch[u][c]=sz++;} u=ch[u][c]; } val[x]=u; } void GetFail() { queue<int> q; fail[0]=0; for(int i=0;i<26;++i) if(ch[0][i]) {fail[ch[0][i]]=0;q.push(ch[0][i]);} while(!q.empty()) { int u=q.front();q.pop(); vec[fail[u]].push_back(u); for(int i=0;i<26;++i) { int c=ch[u][i]; if(!c){ch[u][i]=ch[fail[u]][i];continue;} q.push(c); fail[c]=ch[fail[u]][i]; } } } void dfs(int u) { in[u]=++tim; for(int i=0,len=vec[u].size();i<len;++i) dfs(vec[u][i]); out[u]=tim; } struct Tree{ int ls,rs; int num; } tr[maxn*40]; inline void Update(int y,int &x,int l,int r,int pos) { tr[++tot]=tr[y];tr[tot].num++; x=tot; if(l==r) return; int mid=l+r>>1; if(pos<=mid) Update(tr[y].ls,tr[x].ls,l,mid,pos); else Update(tr[y].rs,tr[x].rs,mid+1,r,pos); } inline void Build(int y,int &x,int id) { int now=0,last=y; for(int i=0;i<Len[id];++i) { now=ch[now][ts[id][i]]; Update(last,x,1,tim,in[now]); last=x; } } inline int Query(int y,int x,int l,int r,int L,int R) { if(L<=l&&r<=R) return tr[x].num-tr[y].num; int mid=l+r>>1,res=0; if(L<=mid) res+=Query(tr[y].ls,tr[x].ls,l,mid,L,R); if(R>mid) res+=Query(tr[y].rs,tr[x].rs,mid+1,r,L,R); return res; } int main() { scanf("%d%d",&N,&Q); Init(); for(int i=1;i<=N;++i) scanf("%s",s),Insert(s,i); GetFail(); dfs(0); for(int i=1;i<=N;++i) Build(T[i-1],T[i],i); while(Q--) { int l,r,x; scanf("%d%d%d",&l,&r,&x); printf("%d\n",Query(T[l-1],T[r],1,tim,in[val[x]],out[val[x]])); } return 0; }