題解-CF163E e-Government
阿新 • • 發佈:2020-07-26
題面
給 \(n\) 個字串 \(s_i\) 和 \(q\) 個詢問,剛開始字串都服役。每次操作將集合中的一個字串設為退役或服役,或查詢與文字串 \(S_i\) 的匹配的服役字串總次數。
資料範圍:\(1\le n,q\le 10^5\),\(1\le \sum|s_i|,\sum|S_i|\le 10^6\)。
蒟蒻語
這是個AC自動機的套路題,但是畢竟套路巧妙而且不得不學,所以蒟蒻寫一篇題解。
蒟蒻解
當這題的字串不退役時,這就是AC自動機的模板。
回憶一下蒟蒻們是怎麼做的:先建一棵Trie樹,在有字串終止節點的位置 \(tag=1\)。然後考慮到包含一個字串必然包含一個字串的字尾,建立 \(fail\)
讓一個字串退役,就相當於將該字串在Trie樹上的終止節點 \(p\) 的 \(tag=1\) 變成 \(tag=0\)。建AC自動機重算 \(tag\) 的時候,每個在 \(parent\) 樹上到根節點的路徑上包含 \(p\) 的節點的 \(tag\) 都會減 \(1\)。容易發現 \(tag\) 減了 \(1\) 的節點,正好就是 \(parent\) 樹上 \(p\) 的子樹。
這時候就可以做了,巨佬可以寫個樹鏈剖分或LinkCutTree。但是考慮到這題只需要操作子樹,不需要操作鏈,所以可以不寫輕重鏈剖分,求每個節點的 \(dfs\) 序及其子樹的 \(dfs\) 序區間即可。區間修改、單點查詢可以用差分加樹狀陣列。
當然這題有很多細節,而且程式碼很長,估計能寫寫調調好久……看蒟蒻程式碼吧。
程式碼
#include <bits/stdc++.h> using namespace std; //Start typedef long long ll; typedef double db; #define mp(a,b) make_pair(a,b) #define x first #define y second #define be(a) a.begin() #define en(a) a.end() #define sz(a) int((a).size()) #define pb(a) push_back(a) const int inf=0x3f3f3f3f; const ll INF=0x3f3f3f3f3f3f3f3f; //Data const int N=1e5,M=1e6+1; int n,at[N]; bool vis[N]; //FenwickTree int c[M+2]; void add(int x,int v){ for(int i=x+1;i<M+2;i+=i&-i) c[i]+=v; } int sum(int x){ int res=0; for(int i=x+1;i>=1;i-=i&-i) res+=c[i]; return res; } //ACAM int cnt=1,ch[M][26]; void insert(int x,string&s){ int p=0; for(int i=0;i<sz(s);i++){ int c=s[i]-'a'; if(!~ch[p][c]) ch[p][c]=cnt++; p=ch[p][c]; } at[x]=p; //記錄第x個字串的終止節點,方便查詢dfs序 } int fa[M],ind,ld[M],rd[M];//[ld,rd)是自動機節點的子樹dfs序區間,ld正好是該節點的dfs序 vector<int> e[M]; void Dfs(int p){ ld[p]=ind++; for(int v:e[p]) Dfs(v); rd[p]=ind; } void build(){ queue<int> q; for(int c=0;c<26;c++) if(~ch[0][c]){ fa[ch[0][c]]=0; e[0].pb(ch[0][c]); //加邊建parent樹 // cout<<0<<"->"<<ch[0][c]<<'\n'; q.push(ch[0][c]); } else ch[0][c]=0; while(sz(q)){ int p=q.front(); q.pop(); for(int c=0;c<26;c++) if(~ch[p][c]){ fa[ch[p][c]]=ch[fa[p]][c]; e[fa[ch[p][c]]].pb(ch[p][c]); //加邊建parent樹 // cout<<fa[ch[p][c]]<<"->"<<ch[p][c]<<'\n'; q.push(ch[p][c]); } else ch[p][c]=ch[fa[p]][c]; } Dfs(0); } //Main int main(){ ios::sync_with_stdio(0); cin.tie(0),cout.tie(0); int T; cin>>T>>n; for(int p=0;p<M;p++){ fa[p]=-1; for(int c=0;c<26;c++) ch[p][c]=-1; } for(int i=0;i<n;i++){ string s; cin>>s; insert(i,s); } build(); for(int i=0;i<n;i++) //剛開始字串都服役 vis[i]=1,add(ld[at[i]],1),add(rd[at[i]],-1); while(T--){ char c; cin>>c; if(c=='+'){ int i; cin>>i,--i; if(vis[i]) continue; vis[i]=1,add(ld[at[i]],1),add(rd[at[i]],-1); } else if(c=='-'){ int i; cin>>i,--i; if(!vis[i]) continue; vis[i]=0,add(ld[at[i]],-1),add(rd[at[i]],1); } else if(c=='?'){ string s; cin>>s; int res=0,p=0; for(int i=0;i<sz(s);i++){ int c=s[i]-'a'; p=ch[p][c],res+=sum(ld[p]); } cout<<res<<'\n'; } } return 0; }
祝大家學習愉快!