●BZOJ 3926 [Zjoi2015]諸神眷顧的幻想鄉
阿新 • • 發佈:2018-03-10
!= open while print else AD class ans 祖先
題鏈:
http://www.lydsy.com/JudgeOnline/problem.php?id=3926
題解&&代碼:
後綴自動機,Trie樹
如果以每個葉子為根,所有的子串一定在某一顆樹的一條由祖先到子孫的鏈上。
由於葉子節點只有不超過20個,那麽就可以從每個葉子開始dfs,把每個從根開始的串都加入一顆trie樹。
顯然,所有的子串都在trie樹上,那麽現在就需要統計trie樹上有多少不同的子串。
對trie樹建立後綴自動機,然後統計不同的子串個數即可。
(本人不會在線建立trie樹的後綴自動機,所以就寫了一個離線BFS trie樹建後綴自動機)
#include<bits/stdc++.h> #define MAXN 100005 #define ll long long using namespace std; ll cnt[MAXN*20]; int color[MAXN],N,C; struct Edge{ int ent; int to[MAXN*2],nxt[MAXN*2],head[MAXN]; Edge(){ent=2;} void Adde(int u,int v){ to[ent]=v; nxt[ent]=head[u]; head[u]=ent++; to[ent]=u; nxt[ent]=head[v]; head[v]=ent++; } }E; struct Trie{ int size; int ch[MAXN*20][10]; int Trans(int last,int x){ if(ch[last][x]) return ch[last][x]; return ch[last][x]=++size; } void Reset(){size=1;} }T; struct SAM{ int size; int maxs[MAXN*20],trans[MAXN*20][10],parent[MAXN*20]; int Newnode(int a,int b){ ++size; maxs[size]=a; memcpy(trans[size],trans[b],sizeof(trans[b])); return size; } int Extend(int last,int x){ static int p,np,q,nq; p=last; np=Newnode(maxs[p]+1,0); for(;p&&!trans[p][x];p=parent[p]) trans[p][x]=np; if(!p) parent[np]=1; else{ q=trans[p][x]; if(maxs[p]+1!=maxs[q]){ nq=Newnode(maxs[p]+1,q); parent[nq]=parent[q]; parent[q]=parent[np]=nq; for(;p&&trans[p][x]==q;p=parent[p]) trans[p][x]=nq; } else parent[np]=q; } return np; } void Reset(){ memset(trans[0],0,sizeof(trans[0])); size=0; Newnode(0,0); } void Count(){ static queue<int>Q; static int order[MAXN*20],in[MAXN*20],ont; for(int p=1;p<=size;p++) for(int c=0;c<10;c++) if(trans[p][c]) in[trans[p][c]]++; Q.push(1); while(!Q.empty()){ int p=Q.front(); Q.pop(); order[++ont]=p; for(int c=0;c<10;c++) if(trans[p][c]){ in[trans[p][c]]--; if(!in[trans[p][c]]) Q.push(trans[p][c]); } } for(int i=size,p;i;i--){ p=order[i]; cnt[p]=(p==1?0:1); for(int c=0;c<10;c++) if(trans[p][c]) cnt[p]+=cnt[trans[p][c]]; } } }SUF; void dfs(int u,int dad,int p){ p=T.Trans(p,color[u]); for(int i=E.head[u];i;i=E.nxt[i]) if(E.to[i]!=dad) dfs(E.to[i],u,p); } void bfs(){ static int state[MAXN*20]; static queue<int>Q; Q.push(1); state[1]=1; while(!Q.empty()){ int u=Q.front(); Q.pop(); for(int c=0;c<10;c++) if(T.ch[u][c]){ state[T.ch[u][c]]=SUF.Extend(state[u],c); Q.push(T.ch[u][c]); } } } int main(){ //freopen("substring.in","r",stdin); //freopen("substring.out","w",stdout); static int in[MAXN]; scanf("%d%d",&N,&C); SUF.Reset(); T.Reset(); for(int i=1;i<=N;i++) scanf("%d",&color[i]); for(int i=1,a,b;i<N;i++) scanf("%d%d",&a,&b),E.Adde(a,b),in[a]++,in[b]++; for(int i=1;i<=N;i++) if(in[i]==1) dfs(i,0,1); bfs(); SUF.Count(); printf("%lld\n",cnt[1]); return 0; }
然後看了別人的做法,發現利用後綴自動機裏面每個狀態的不重復的性質性,還可以有更簡便的求不同子串個數的方法(SUF.Count()有變化,效率提升了些)。
#include<bits/stdc++.h> #define MAXN 100005 #define ll long long using namespace std; ll cnt; int color[MAXN],N,C; struct Edge{ int ent; int to[MAXN*2],nxt[MAXN*2],head[MAXN]; Edge(){ent=2;} void Adde(int u,int v){ to[ent]=v; nxt[ent]=head[u]; head[u]=ent++; to[ent]=u; nxt[ent]=head[v]; head[v]=ent++; } }E; struct Trie{ int size; int ch[MAXN*20][10]; int Trans(int last,int x){ if(ch[last][x]) return ch[last][x]; return ch[last][x]=++size; } void Reset(){size=1;} }T; struct SAM{ int size; int maxs[MAXN*20],trans[MAXN*20][10],parent[MAXN*20]; int Newnode(int a,int b){ ++size; maxs[size]=a; memcpy(trans[size],trans[b],sizeof(trans[b])); return size; } int Extend(int last,int x){ static int p,np,q,nq; p=last; np=Newnode(maxs[p]+1,0); for(;p&&!trans[p][x];p=parent[p]) trans[p][x]=np; if(!p) parent[np]=1; else{ q=trans[p][x]; if(maxs[p]+1!=maxs[q]){ nq=Newnode(maxs[p]+1,q); parent[nq]=parent[q]; parent[q]=parent[np]=nq; for(;p&&trans[p][x]==q;p=parent[p]) trans[p][x]=nq; } else parent[np]=q; } return np; } void Reset(){ memset(trans[0],0,sizeof(trans[0])); size=0; Newnode(0,0); } void Count(){ for(int p=1;p<=size;p++) cnt+=maxs[p]-maxs[parent[p]]; } }SUF; void dfs(int u,int dad,int p){ p=T.Trans(p,color[u]); for(int i=E.head[u];i;i=E.nxt[i]) if(E.to[i]!=dad) dfs(E.to[i],u,p); } void bfs(){ static int state[MAXN*20]; static queue<int>Q; Q.push(1); state[1]=1; while(!Q.empty()){ int u=Q.front(); Q.pop(); for(int c=0;c<10;c++) if(T.ch[u][c]){ state[T.ch[u][c]]=SUF.Extend(state[u],c); Q.push(T.ch[u][c]); } } } int main(){ // freopen("substring.in","r",stdin); // freopen("substring.out","w",stdout); static int in[MAXN]; scanf("%d%d",&N,&C); SUF.Reset(); T.Reset(); for(int i=1;i<=N;i++) scanf("%d",&color[i]); for(int i=1,a,b;i<N;i++) scanf("%d%d",&a,&b),E.Adde(a,b),in[a]++,in[b]++; for(int i=1;i<=N;i++) if(in[i]==1) dfs(i,0,1); bfs(); SUF.Count(); printf("%lld\n",cnt); return 0; }
然後想去學學在線對trie樹建立後綴自動機,但是論文看得我腦袋疼。。。
這時突然發現其他博主的代碼也是可以在線增量的,似乎叫廣義後綴自動機。。。,
感覺看代碼的實現似乎沒毛病,而且還避免了建trie樹。只是出現了一些無法到達的狀態。
#include<bits/stdc++.h> #define MAXN 100005 #define ll long long using namespace std; int N,C; int color[MAXN]; struct Edge{ int to[MAXN*2],nxt[MAXN*2],head[MAXN],ent; Edge(){ent=2;} void Adde(int u,int v){ to[ent]=v; nxt[ent]=head[u]; head[u]=ent++; to[ent]=u; nxt[ent]=head[v]; head[v]=ent++; } }E; struct SAM{ int size; int maxs[MAXN*20],trans[MAXN*20][26],parent[MAXN*20]; int Newnode(int a,int b){ ++size; maxs[size]=a; memcpy(trans[size],trans[b],sizeof(trans[b])); return size; } int Extend(int last,int x){ static int p,np,q,nq; p=last; if(trans[p][x]&&maxs[p]+1==maxs[trans[p][x]]) return trans[p][x]; np=Newnode(maxs[p]+1,0); for(;p&&!trans[p][x];p=parent[p]) trans[p][x]=np; if(!p) parent[np]=1; else{ q=trans[p][x]; if(maxs[p]+1!=maxs[q]){ nq=Newnode(maxs[p]+1,q); parent[nq]=parent[q]; parent[q]=parent[np]=nq; for(;p&&trans[p][x]==q;p=parent[p]) trans[p][x]=nq; } else parent[np]=q; } return np; } void Reset(){ memset(trans[0],0,sizeof(trans[0])); size=0; Newnode(0,0); } ll Count(ll cnt=0){ for(int p=1;p<=size;p++) cnt+=maxs[p]-maxs[parent[p]]; return cnt; } }SUF; void dfs(int u,int dad,int last){ last=SUF.Extend(last,color[u]); for(int i=E.head[u];i;i=E.nxt[i]) if(E.to[i]!=dad) dfs(E.to[i],u,last); } int main(){ freopen("substring.in","r",stdin); //freopen("substring.out","w",stdout); static int in[MAXN]; SUF.Reset(); scanf("%d%d",&N,&C); for(int i=1;i<=N;i++) scanf("%d",&color[i]); for(int i=1,u,v;i<N;i++) scanf("%d%d",&u,&v),E.Adde(u,v),in[u]++,in[v]++; for(int i=1;i<=N;i++) if(in[i]==1) dfs(i,0,1); printf("%lld\n%d\n",SUF.Count(),SUF.size); return 0; }
結果是比之前建了trie樹的離線自動機構法多了一些狀態,
(第20組數據做的測試,相比於建立Trie樹後再離線bfs建後綴自動機多了2w個狀態,估計就是那些無法到達的狀態產生的)
然後我想:“如果還是先建立一顆trie樹,再用上面的增量法去在線構造Trie樹的後綴自動機,會不會減少一些無法到達的狀態?”
試了一下,結果更慢了。。。(第20組數據做的測試,相比與不建Trie樹直接在線增量法構造後綴自動機,多了1ow個狀態。。。)
就不放代碼了。
●BZOJ 3926 [Zjoi2015]諸神眷顧的幻想鄉