Codeforces 666E Forensic Examination SAM or SA+线段树合并
E. Forensic Examination
http://codeforces.com/problemset/problem/666/E
题目大意:给模式串S以及m个特殊串,q个询问,询问S的子串[pl,pr]在特殊串编号属于[l,r]中出现次数最多的次数以及在哪个特殊串。
一开始打算用S建SAM,特殊串去匹配。。。测样例时才想起这样不对。胡搞一番后,才开始写下面的做法。
S与特殊串连在一起建SAM,记录S串[1,x]在SAM上节点位置,用来子串定位。每个节点的{right}该串出现的所有地方。于是题目成了求一个节点的{right}中属于某个特殊串次数最多的特殊串。给这些{right}染色,即标记属于哪个特殊串。一个节点用一棵线段树维护最值,然后用线段树合并求出每个节点的线段树。
复杂度O(nlogn)。。。这n大概为106
#include<cstdio> #include<cmath> #include<cstring> const int len(500000),lem(35000000),N(600000); struct Node{int pre,nx[27],step;}sam[N*2+10]; int top=1,now=1,root=1,last,lastson;int n,pos[len+10]; int m,q,leng,l,r,pl,pr;char str[len+10]; int f[22][N*2+10],logg; struct SegMent{int nx[2];unsigned short int big,pos;}tree[lem+10]; int stot,rt[N*2+10]; struct data{unsigned short int x,y;}; void extend(int x) { last=now;now=++top;sam[now].step=sam[last].step+1; for(;!sam[last].nx[x]&&last;last=sam[last].pre) sam[last].nx[x]=now; if(!last)sam[now].pre=root; else { lastson=sam[last].nx[x]; if(sam[lastson].step==sam[last].step+1)sam[now].pre=lastson; else { sam[++top]=sam[lastson];sam[top].step=sam[last].step+1; sam[now].pre=sam[lastson].pre=top; for(;sam[last].nx[x]==lastson&&last;last=sam[last].pre) sam[last].nx[x]=top; } } } void update(int k) { int next; if(tree[tree[k].nx[0]].big>tree[tree[k].nx[1]].big|| (tree[tree[k].nx[0]].big==tree[tree[k].nx[1]].big&& tree[tree[k].nx[0]].pos<tree[tree[k].nx[1]].pos))next=0; else next=1; tree[k].big=tree[tree[k].nx[next]].big; tree[k].pos=tree[tree[k].nx[next]].pos; } void insert(int &k,int l,int r,int x) { if(!k)k=++stot; if(l==r){tree[k].big++;tree[k].pos=x;return;} int mid=(l+r)>>1; if(x<=mid)insert(tree[k].nx[0],l,mid,x); else insert(tree[k].nx[1],mid+1,r,x); update(k); } int find(int l,int r) { int x=pos[r]; for(int j=logg;j>=0;j--) if(sam[f[j][x]].step>=r-l+1)x=f[j][x]; return x; } int merge(int x,int y,int l,int r) { if(!x||!y)return x|y; int z=++stot; if(l==r){tree[z].big=tree[x].big+tree[y].big;tree[z].pos=tree[x].pos;return z;} int mid=(l+r)>>1; tree[z].nx[0]=merge(tree[x].nx[0],tree[y].nx[0],l,mid); tree[z].nx[1]=merge(tree[x].nx[1],tree[y].nx[1],mid+1,r); update(z); return z; } int cnt[N*2+10],p[N*2+10]; void update() { for(int i=1;i<=top;i++)cnt[sam[i].step]++; for(int i=1;i<=top;i++)cnt[i]+=cnt[i-1]; for(int i=top;i>=1;i--)p[cnt[sam[i].step]--]=i; for(int i=top;i>=1;i--) rt[sam[p[i]].pre]=merge(rt[sam[p[i]].pre],rt[p[i]],1,m); } void deal() { logg=log(top)/log(2); for(int i=1;i<=top;i++)f[0][i]=sam[i].pre; for(int j=1;j<=logg;j++) for(int i=1;i<=top;i++) f[j][i]=f[j-1][f[j-1][i]]; } data query(int k,int l,int r,int L,int R) { if(!k)return (data){0,l}; if(L==l&&R==r)return (data){tree[k].big,tree[k].pos}; int mid=(L+R)>>1; if(r<=mid)return query(tree[k].nx[0],l,r,L,mid); else if(mid<l)return query(tree[k].nx[1],l,r,mid+1,R); else { data t1=query(tree[k].nx[0],l,mid,L,mid),t2=query(tree[k].nx[1],mid+1,r,mid+1,R); if(t1.x>t2.x||(t1.x==t2.x&&t1.y<t2.y))return t1; else return t2; } } void read(int &x) { x=0;char ch=getchar(); while(ch<'0'||ch>'9')ch=getchar(); while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar(); } int main() { freopen("C.in","r",stdin); freopen("C2.out","w",stdout); scanf("%s",str); n=strlen(str); for(int i=0;i<n;i++)extend(str[i]-'a'),pos[i+1]=now; extend(26); scanf("%d",&m); for(int i=1;i<=m;i++) { scanf("%s",str);leng=strlen(str); for(int j=0;j<leng;j++)extend(str[j]-'a'),insert(rt[now],1,m,i); extend(26); } deal();update(); scanf("%d",&q); for(int i=1;i<=q;i++) { scanf("%d%d%d%d",&l,&r,&pl,&pr); int x=find(pl,pr);data tmp; tmp=query(rt[x],l,r,1,m); if(tmp.x==0)tmp.y=l; printf("%d %d\n",tmp.y,tmp.x); } return 0; }
第二种写法:
将串连在一起求SA,设L为小于rank[pl]的第一个height[L]=pr-pl+1,R为大于rank[pl]的第一个height[R]=pr-pl+1。那么[pl,pr]能匹配的后缀区间即[L,R]。给后缀标记一下属于哪个特殊串,将题目变成了求区间众数。有趣的是这些区间不是包含关系就是相离关系。
令height[i]=(i-1,i)的(无向)边权,那么一次询问就是询问点rank[pl]只经过边权大于等于(pr-pl+1)的边所能到达点中出现次数最多的特殊串。
于是变成B3545Peaks加强版,用线段树维护,线段树合并实现。
O(nlogn)
#include<cstdio> #include<cmath> #include<cstring> #include<vector> const int limt(28),len(600000),lem(40000000); int tmp[len+10],cnt[len+10],p[len+10]; int rank[len*2+10],sfa[len+10],height[len+10]; int str[len+10],color[len+10],val[len*2+10]; int n,m,Q,leng,l,r,pl,pr,x;char ch[len+10]; struct Node{int nd,nx,co;}bot[len*2+10];int tot,first[len*2+10]; int g[21][len*2+10],f[21][len*2+10],logg; int father[len*2+10],now,last; std::vector<int>weight[len+10]; struct ANS { unsigned short int big,pos; inline ANS operator +(const ANS&A)const{return (ANS){big+A.big,pos};} inline ANS operator *(const ANS&A)const{if(A.big<big||(big==A.big&&pos<A.pos))return (ANS){big,pos};return A;} }ans; struct SegMent{int nx[2];ANS key;}tree[lem+10];int stot,root[len*2+10]; void read(int &x) { x=0;int f=1;char ch=getchar(); while((ch<'0'||ch>'9')&&(ch!='-')){ch=getchar();}if(ch=='-')f=-1,ch=getchar(); while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} if(f<0)x=-x; } void add(int a,int b,int c){bot[++tot]=(Node){b,first[a],c};first[a]=tot;} void swap(int &a,int &b){if(a==b)return;a^=b;b^=a;a^=b;} int min(int a,int b){return a>b?b:a;} bool com(int x,int y,int l){return rank[x]==rank[y]&&rank[x+l]==rank[y+l];} void doubling() { for(int i=1;i<=n;i++)rank[i]=str[i],sfa[i]=i; for(int pos=0,l=0,sigma=limt;pos<n;sigma=pos) { pos=0; for(int i=n-l+1;i<=n;i++)p[++pos]=i; for(int i=1;i<=n;i++)if(sfa[i]>l)p[++pos]=sfa[i]-l; memset(cnt,0,sizeof(int)*(sigma+1));pos=0; for(int i=1;i<=n;i++)cnt[rank[i]]++; for(int i=1;i<=sigma;i++)cnt[i]+=cnt[i-1]; for(int i=n;i>=1;i--)sfa[cnt[rank[p[i]]]--]=p[i]; for(int i=1;i<=n;i++)tmp[sfa[i]]=com(sfa[i],sfa[i-1],l)?pos:++pos; for(int i=1;i<=n;i++)rank[i]=tmp[i]; l=!l?1:l<<1; } for(int i=1;i<=n;i++)rank[sfa[i]]=i; for(int i=1,j,k;i<=n;i++) { k=sfa[rank[i]-1]; if(!k)continue; j=height[rank[i-1]]; if(j)j--; while(str[i+j]==str[k+j]) j++; height[rank[i]]=j; } } int gf(int x) { int v=x,o;while(v!=father[v])v=father[v]; for(;x!=father[x];x=o){o=father[x];father[x]=v;} return v; } void exchange() { for(int i=2;i<=n;i++)weight[height[i]].push_back(i); for(int i=1;i<=n;i++)val[i]=color[sfa[i]]; for(int i=1;i<=n*2;i++)father[i]=i; now=n;last=now; for(int i=n,fa,fb;i>=1;i--) { last=now; for(int v=0;v<(int)weight[i].size();v++) { int t=weight[i][v]; fa=gf(weight[i][v]-1);fb=gf(weight[i][v]); if(fa!=fb) { now++; father[fb]=father[fa]=father[now]; if(now!=fb)add(now,fb,i); if(now!=fa)add(now,fa,i); } } } now++; for(int i=1,fa;i<=n;i++) { fa=gf(i); if(fa!=now)add(now,fa,0);father[fa]=now; } } void update(int k){tree[k].key=tree[tree[k].nx[0]].key*tree[tree[k].nx[1]].key;} void insert(int &k,int l,int r,int x) { if(!k)k=++stot; if(l==r){tree[k].key.big++;tree[k].key.pos=l;return;} int mid=(l+r)>>1; if(x<=mid)insert(tree[k].nx[0],l,mid,x); else insert(tree[k].nx[1],mid+1,r,x); update(k); } int merge(int x,int y,int l,int r) { if(!x||!y)return (x|y); int z=++stot,mid=(l+r)>>1; if(l==r){tree[z].key=tree[x].key+tree[y].key;return z;} tree[z].nx[0]=merge(tree[x].nx[0],tree[y].nx[0],l,mid); tree[z].nx[1]=merge(tree[x].nx[1],tree[y].nx[1],mid+1,r); update(z); return z; } void dfs(int x) { if(val[x])insert(root[x],1,m,val[x]); for(int v=first[x];v;v=bot[v].nx) { dfs(bot[v].nd); f[0][bot[v].nd]=x; g[0][bot[v].nd]=bot[v].co; root[x]=merge(root[x],root[bot[v].nd],1,m); // fprintf(stdout,"%d\n",stot); } } void Fdeal() { logg=log(now)/log(2); for(int j=1;j<=logg;j++) for(int i=1;i<=now;i++) f[j][i]=f[j-1][f[j-1][i]],g[j][i]=min(g[j-1][i],g[j-1][f[j-1][i]]); } int find(int pl,int pr) { int x=rank[pl]; for(int j=logg;j>=0;j--) if(g[j][x]>=pr-pl+1)x=f[j][x]; return x; } ANS query(int k,int L,int R,int l,int r) { if(!k)return (ANS){0,l}; if(l==L&&r==R)return tree[k].key; int mid=(L+R)>>1; if(r<=mid)return query(tree[k].nx[0],L,mid,l,r); else if(mid<l)return query(tree[k].nx[1],mid+1,R,l,r); else return query(tree[k].nx[0],L,mid,l,mid)*query(tree[k].nx[1],mid+1,R,mid+1,r); } int main() { // freopen("C.in","r",stdin); // freopen("C.out","w",stdout); scanf("%s",ch); leng=strlen(ch); for(int i=0;i<leng;i++)str[++n]=ch[i]-'a'+1; str[++n]=27; scanf("%d",&m); for(int i=1;i<=m;i++) { scanf("%s",ch); leng=strlen(ch); for(int j=0;j<leng;j++)str[++n]=ch[j]-'a'+1,color[n]=i; str[++n]=27; } doubling(); exchange();//将序列变成树 dfs(now); Fdeal(); scanf("%d",&Q); for(int i=1;i<=Q;i++) { read(l);read(r);read(pl);read(pr); x=find(pl,pr); ans=query(root[x],1,m,l,r); printf("%d %d\n",ans.pos,ans.big); } return 0; }