HDU 4787 GRE Words Revenge 在线AC自动机
题意:多次操作和询问,操作是增加新的模版串,询问是询问匹配。
思路:如果每次插入重建AC自动机的话,需要重建n次,每次重建复杂度为n^2。
弄两个AC自动机A和B,B限制结点数上限为sqrt(n),每次增加新的模版串的时候将它插到B中,重建B,当B结点数达到sqrt(n)时,将B合并到A中,重建A。每次查询在A和B,取和即可。复杂度:重建B为sqrt(n),对B重建n次,故耗在B中的复杂度为n*sqrt(n),重建A为n,重建n/sqrt(n)=sqrt(n)次,在A中的复杂度也是n*sqrt(n),合并的复杂度为sqrt(n),合并sqrt(n)次,故耗在合并的复杂度为n(不包括重建A),因此总复杂度为o(n*sqrt(n)+n*sqrt(n)+n)=o(n*sqrt(n))。这样就将本来n^2的复杂度均摊成了n*sqrt(n)。。。
由于重建的时候如果按平时那样将真实边与失配边一视同仁的话,重建的时候处理起来比较麻烦(我还没想好怎么处理),所以这里我将失配边和真实边区分开,这里需要注意建立AC自动机的时候如果求f[ch[u][c]]需要沿着失配边往回走而不能直接设为ch[f[u]][c],因为这里已经将真实边与失配边区分开了,所以ch[f[u]][c]可能不存在。另外一个要注意的是重复出现的模版串,我的处理是弄一个vis数组判重,查完后再查一遍将vis清零,不直接memset当然是考虑到复杂度的问题。通过构造数据我已经发现了这两个可能出错的地方,调了一下交上去,本来以为应该AC的,然而还是WA了。。。为何这样作弄我!!!
先留着代码,吃完饭回来继续调。。。
#include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #include<queue> #define REP(i,a,b) for(int i=a;i<=b;i++) #define MS0(a) memset(a,0,sizeof(a)) using namespace std; typedef long long ll; const int maxn=5000100; const int INF=1e9+10; int q; char s[maxn],t[maxn]; bool vis[maxn]; struct Trie { int ch[maxn][2]; int End[maxn]; int last[maxn]; int f[maxn]; int rt,tot; int newnode() { ++tot; memset(ch[tot],-1,sizeof(ch[tot])); End[tot]=0; return tot; } void init() { tot=-1; rt=newnode(); } void insert(char *s) { int len=strlen(s),u=rt; REP(i,0,len-1){ int c=s[i]-'0'; if(ch[u][c]==-1) ch[u][c]=newnode(); u=ch[u][c]; } End[u]++; } int get(int u) { if(u==rt||vis[u]) return 0; vis[u]=1; return End[u]+get(last[u]); } void cls(int u) { if(u==rt||vis[u]==0) return; vis[u]=0; cls(last[u]); } void recover(char *s) { int len=strlen(s),u=rt; REP(i,0,len-1){ int c=s[i]-'0'; while(ch[u][c]==-1&&u!=rt) u=f[u]; if(ch[u][c]==-1) continue; u=ch[u][c]; if(End[u]) cls(u); else if(last[u]) cls(last[u]); } } int find(char *s) { int len=strlen(s),u=rt; int res=0; REP(i,0,len-1){ int c=s[i]-'0'; while(ch[u][c]==-1&&u!=rt) u=f[u]; if(ch[u][c]==-1) continue; u=ch[u][c]; if(End[u]) res+=get(u); else if(last[u]) res+=get(last[u]); } recover(s);/// search again to clear the vis return res; } void build() { queue<int> q; f[rt]=rt;last[rt]=rt; REP(c,0,1){ if(~ch[rt][c]) f[ch[rt][c]]=rt,q.push(ch[rt][c]); ///else ch[rt][c]=rt; } while(!q.empty()){ int u=q.front();q.pop(); REP(c,0,1){ if(~ch[u][c]){ int t=f[u]; while(ch[t][c]==-1&&t!=rt) t=f[t]; if(ch[t][c]==-1) f[ch[u][c]]=rt; else f[ch[u][c]]=ch[t][c]; q.push(ch[u][c]); } ///else ch[u][c]=ch[f[u]][c]; } if(End[f[u]]) last[u]=f[u]; else last[u]=last[f[u]]; } } };Trie A,B; void Insert(int u,int v) { A.End[u]+=B.End[v]; REP(c,0,1){ if(~B.ch[v][c]){ if(A.ch[u][c]==-1) A.ch[u][c]=A.newnode(); Insert(A.ch[u][c],B.ch[v][c]); } } } void join() { Insert(A.rt,B.rt); B.init();B.build(); A.build(); } void Move(char *s,int k) { int len=strlen(s); REP(i,0,len) t[i]=s[i]; REP(i,k,len-1) s[i-k]=t[i]; REP(i,1,k) s[len-i]=t[k-i]; s[len]='\0'; } int main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); #endif // ONLINE_JUDGE int T;cin>>T;int casen=1; while(T--){ scanf("%d",&q); A.init(); B.init(); A.build(); B.build(); printf("Case #%d:\n",casen++); MS0(vis); int ans=0; while(q--){ scanf("%s",s); if(s[0]=='+'){ B.insert(s+1); B.build(); if(B.tot+1>2010) join();/// join B to A } else{ Move(s+1,ans); //cout<<"s="<<s<<endl; ans=A.find(s+1)+B.find(s+1); //cout<<" A="<<A.find(s+1)<<" B="<<B.find(s+1)<<endl; printf("%d\n",ans); } } } return 0; } /** 4 3 +01 +01 ?01001 3 +01 ?010 ?011 7 +10010 +10010 ?00 ?0 ?10001010010010 +1111 ?1100101111 5 +111 +1010 ?10101111 +1111 ?00101111 2 5 +111 +1010 ?10101111 +1111 ?00101111 4 +111 +1010 +1111 ?10111100 */
悲剧地发现了题目看错。。。原来是找有询问的串中包含了多少模板串,模板串要去掉重复的。。。这样就不需要vis数组清零了。。。直接沿着失配边往回走就可以了,每次往回走实际上是遍历该结点所表示的前缀的后缀,前缀的后缀就是所有的子串了。。。
然而还是WA了。。。
#include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #include<queue> #define REP(i,a,b) for(int i=a;i<=b;i++) #define MS0(a) memset(a,0,sizeof(a)) using namespace std; typedef long long ll; const int maxn=5000100; const int INF=1e9+10; int q; char s[maxn],t[maxn]; struct Trie { int ch[maxn][2]; int f[maxn]; int last[maxn]; int End[maxn]; int rt,tot; int newnode() { ++tot; memset(ch[tot],-1,sizeof(ch[tot])); End[tot]=0; return tot; } void init() { tot=-1; rt=newnode(); } void insert(char *s) { int len=strlen(s),u=rt; REP(i,0,len-1){ int c=s[i]-'0'; if(ch[u][c]==-1) ch[u][c]=newnode(); u=ch[u][c]; } End[u]=1; } void build() { queue<int> q; f[rt]=rt;last[rt]=rt; REP(c,0,1){ if(~ch[rt][c]) f[ch[rt][c]]=rt,q.push(ch[rt][c]); } while(!q.empty()){ int u=q.front();q.pop(); REP(c,0,1){ if(~ch[u][c]){ int t=f[u]; while(ch[t][c]==-1&&t!=rt) t=f[t]; if(ch[t][c]==-1) f[ch[u][c]]=rt; else f[ch[u][c]]=ch[t][c]; q.push(ch[u][c]); } } if(End[f[u]]) last[u]=f[u]; else last[u]=last[f[u]]; } } int get(int u) { if(u==rt) return 0; return End[u]+get(last[u]); } int find(char *s) { int len=strlen(s),u=rt; int res=0; REP(i,0,len-1){ int c=s[i]-'0'; while(ch[u][c]==-1&&u!=rt) u=f[u]; if(ch[u][c]==-1) continue; u=ch[u][c]; if(End[u]) res+=get(u); else if(last[u]) res+=get(last[u]); } return res; } bool has(char *s) { int len=strlen(s),u=rt; REP(i,0,len-1){ int c=s[i]-'0'; if(ch[u][c]==-1) return 0; u=ch[u][c]; } return End[u]; } };Trie A,B; void Insert(int u,int v) { A.End[u]|=B.End[v]; REP(c,0,1){ if(~B.ch[v][c]){ if(A.ch[u][c]==-1) A.ch[u][c]=A.newnode(); Insert(A.ch[u][c],B.ch[v][c]); } } } void join() { Insert(A.rt,B.rt);/// insert B to A B.init();B.build(); A.build(); } void MoveL(char *s,int k) { int len=strlen(s); REP(i,0,len) t[i]=s[i]; s[0]='\0'; REP(i,0,len-k-1) s[i]=t[k+i]; REP(i,0,k-1) s[len-k+i]=t[i]; } int main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); #endif // ONLINE_JUDGE int T;cin>>T;int casen=1; while(T--){ scanf("%d",&q); A.init();B.init(); A.build();B.build(); printf("Case #%d:\n",casen++); int ans=0; while(q--){ scanf("%s",s); MoveL(s+1,ans); if(s[0]=='+'){ if(A.has(s+1)||B.has(s+1)) continue; B.insert(s+1); B.build(); if(B.tot>2010) join();/// join B to A } else{ ans=A.find(s+1)+B.find(s+1); printf("%d\n",ans); } } } return 0; } /** 4 3 +01 +01 ?01001 3 +01 ?010 ?011 7 +10010 +10010 ?00 ?0 ?10001010010010 +1111 ?1100101111 5 +111 +1010 ?10101111 +1111 ?00101111 2 5 +111 +1010 ?10101111 +1111 ?00101111 4 +111 +1010 +1111 ?10111100 */
终于过了。。。在AC自动机上调半天,最后发现只是move函数写错了。。。左移k位按原来的写法需要注意k>=len的时候,可以直接让k对len取余。这题真的没什么坑点,不过第一次想出这个思路的人很厉害,后来人理解和实现这个思路确实没什么难度。我真是太傻逼了,在细节上挂半天。。。
#include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #include<queue> #define REP(i,a,b) for(int i=a;i<=b;i++) #define MS0(a) memset(a,0,sizeof(a)) using namespace std; typedef long long ll; const int maxn=5000100; const int INF=1e9+10; int q; char s[maxn],t[maxn]; struct Trie { int ch[maxn][2]; int f[maxn]; int last[maxn]; int End[maxn]; int rt,tot; int newnode() { ++tot; memset(ch[tot],-1,sizeof(ch[tot])); End[tot]=0; return tot; } void init() { tot=-1; rt=newnode(); } void insert(char *s) { int len=strlen(s),u=rt; REP(i,0,len-1){ int c=s[i]-'0'; if(ch[u][c]==-1) ch[u][c]=newnode(); u=ch[u][c]; } End[u]=1; } void build() { queue<int> q; f[rt]=rt;last[rt]=rt; REP(c,0,1){ if(~ch[rt][c]) f[ch[rt][c]]=rt,q.push(ch[rt][c]); } while(!q.empty()){ int u=q.front();q.pop(); REP(c,0,1){ if(~ch[u][c]){ int t=f[u]; while(ch[t][c]==-1&&t!=rt) t=f[t]; if(ch[t][c]==-1) f[ch[u][c]]=rt; else f[ch[u][c]]=ch[t][c]; q.push(ch[u][c]); } } if(End[f[u]]) last[u]=f[u]; else last[u]=last[f[u]]; } } ll get(int u) { if(u==rt) return 0; return End[u]+get(last[u]); } ll find(char *s) { int len=strlen(s),u=rt; ll res=0; REP(i,0,len-1){ int c=s[i]-'0'; while(ch[u][c]==-1&&u!=rt) u=f[u]; if(ch[u][c]==-1) continue; u=ch[u][c]; if(End[u]) res+=get(u); else if(last[u]) res+=get(last[u]); } return res; } bool has(char *s) { int len=strlen(s),u=rt; REP(i,0,len-1){ int c=s[i]-'0'; if(ch[u][c]==-1) return 0; u=ch[u][c]; } return End[u]; } };Trie A,B; void Insert(int u,int v) { A.End[u]|=B.End[v]; REP(c,0,1){ if(~B.ch[v][c]){ if(A.ch[u][c]==-1) A.ch[u][c]=A.newnode(); Insert(A.ch[u][c],B.ch[v][c]); } } } void join() { Insert(A.rt,B.rt);/// insert B to A B.init(); A.build(); } void MoveL(char *s,int k) { int len=strlen(s); REP(i,0,len) t[i]=s[i]; REP(i,0,len-1) s[i]=t[(i+k)%len]; } int main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); #endif // ONLINE_JUDGE int T;cin>>T;int casen=1; while(T--){ scanf("%d",&q); A.init();B.init(); printf("Case #%d:\n",casen++); ll ans=0; while(q--){ scanf("%s",s); MoveL(s+1,ans); if(s[0]=='+'){ if(A.has(s+1)||B.has(s+1)) continue; B.insert(s+1); B.build(); if(B.tot>2010) join();/// join B to A } else{ ans=A.find(s+1)+B.find(s+1); printf("%I64d\n",ans); } } } return 0; } /** 4 3 +01 +01 ?01001 3 +01 ?010 ?011 7 +10010 +10010 ?00 ?0 ?10001010010010 +1111 ?1100101111 5 +111 +1010 ?10101111 +1111 ?00101111 2 5 +111 +1010 ?10101111 +1111 ?00101111 4 +111 +1010 +1111 ?10111100 */