CF666E Forensic Examination
https://www.luogu.org/problemnew/show/CF666E
本质还是一个串在一些串里的匹配
考虑对模板串建广义SAM
既然有S的一些匹配关系的询问,套路地,
把S在上面跑,
pi[i]记录S的[1~i]的前缀在SAM上的匹配长度
pos[i]记录S的[1~i]的前缀在SAM的匹配最终位置
询问的时候,如果匹配长度不够l,那么输出(L,0)
从pos[i]往上倍增,直到len刚好大于pr-pl+1(开始的时候,如果匹配长度不够,直接返回0)
整个子树对应的叶子就是所有出现位置。
没有的话,还要检查,输出(L,0)
至于在[l,r]哪个串出现位置最多,线段树合并即可。
或者dsu on the tree(即区间众数) 拿两个桶维护一下。
一个维护每个值出现次数,以及出现次数为某个次数的值有多少种。
代码细节:
1.线段树合并:由于之前的还要用,所以合并的时候务必新建一个节点,空间变成2倍,但是本质上参与合并的点数还是nlogn的。
否则类似主席树,之前的值会改变,无法查询!
2.记录匹配长度是在S的位置记录!不是一般的在SAM上记录最长匹配位置!(这个简直大错特错,因为可能和原子串根本不符合匹配!长度大于,但是可能是S别的地方匹配过来的!)
3.pi数组的长度是N,不是M(N,M)并不同阶。
代码:
#include<bits/stdc++.h> #define reg register int #define il inline #define mid ((l+r)>>1) #define numb (ch^'0') using namespace std; typedef long long ll; il void rd(int &x){ char ch;x=0;bool fl=false; while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true); for(x=numb;isdigit(ch=getchar());x=x*10+numb); (fl==true)&&(x=-x); } namespace Miracle{ const int N=5e5+5; const int M=1e5+5; int n,m; char s[N],a[M]; pair<int,int> cmp(pair<int,int>a,pair<int,int>b){ if(a.second==b.second) { return a.first<b.first?a:b; } return a.second>b.second?a:b; } struct SAM{ int f[2*M][20],ch[2*M][26],len[2*M]; int nd,cnt; void init(){ nd=cnt=1; } struct node{ int ls,rs; int mx,id; }t[4*M*20]; int rt[2*M]; int pos[N]; int pi[N]; int num; void pushup(int x){ if(t[t[x].ls].mx>t[t[x].rs].mx||(t[t[x].ls].mx==t[t[x].rs].mx&&t[t[x].ls].id<t[t[x].rs].id)){ t[x].mx=t[t[x].ls].mx; t[x].id=t[t[x].ls].id; } else{ t[x].mx=t[t[x].rs].mx; t[x].id=t[t[x].rs].id; } } void upda(int &x,int l,int r,int to,int c){ if(!x) x=++num; if(l==r){ t[x].mx+=c;t[x].id=l;return; } if(to<=mid) upda(t[x].ls,l,mid,to,c); else upda(t[x].rs,mid+1,r,to,c); pushup(x); } int merge(int x,int y,int l,int r){ if(!x||!y) return x+y; int ret=++num; if(l==r){ t[ret].mx=t[x].mx+t[y].mx; t[ret].id=t[x].id; return ret; } t[ret].ls=merge(t[x].ls,t[y].ls,l,mid); t[ret].rs=merge(t[x].rs,t[y].rs,mid+1,r); pushup(ret); return ret; } pair<int,int> query(int x,int l,int r,int L,int R){ //cout<<" quering "<<l<<" "<<r<<" "<<L<<" "<<R<<" :: "<<t[x].id<<" "<<t[x].mx<<endl; if(L<=l&&r<=R){ return make_pair(t[x].id,t[x].mx); } pair<int,int>ret; ret.first=0,ret.second=-2333; if(L<=mid) ret=cmp(ret,query(t[x].ls,l,mid,L,R)); if(mid<R) ret=cmp(ret,query(t[x].rs,mid+1,r,L,R)); return ret; } void ins(int l,int c,int d){ int p=nd;nd=++cnt;len[nd]=l; for(;p&&ch[p][c]==0;p=f[p][0]) ch[p][c]=cnt; int q; if(!p){ f[nd][0]=1;goto abc; } q=ch[p][c]; if(len[q]==len[p]+1){ f[nd][0]=q;goto abc; } len[++cnt]=len[p]+1; f[cnt][0]=f[q][0];f[q][0]=f[nd][0]=cnt; for(reg i=0;i<26;++i) ch[cnt][i]=ch[q][i]; for(;p&&ch[p][c]==q;p=f[p][0]) ch[p][c]=cnt; abc:; upda(rt[nd],1,m,d,1); } void wrk(char *s){ int lth=strlen(s+1); int now=1; int l=0; for(reg i=1;i<=lth;++i){ int x=s[i]-'a'; // cout<<" wrk "<<i<<" "<<now<<" "<<l<<endl; if(ch[now][x]==0){ while(now&&ch[now][x]==0) now=f[now][0]; if(!now){ l=0; pos[i]=1; now=1;pi[i]=0; } else{ l=len[now]+1; now=ch[now][x]; pi[i]=l;pos[i]=now; } }else{ // cout<<" ok "<<endl; ++l; now=ch[now][x]; pi[i]=l;pos[i]=now; } } } struct edge{ int nxt,to; }e[2*M]; int hd[2*M],tot; void add(int x,int y){ e[++tot].nxt=hd[x]; e[tot].to=y; hd[x]=tot; } void dfs(int x){ //cout<<" xx "<<x<<" fa "<<f[x][0]<<endl; //cout<<" rt[21] "<<rt[21]<<" "<<t[rt[21]].id<<" "<<t[rt[21]].mx<<endl; for(reg i=hd[x];i;i=e[i].nxt){ int y=e[i].to; dfs(y); //cout<<" mergeing "<<rt[x]<<" "<<rt[y]<<endl; // cout<<" before rt[21] "<<rt[21]<<" "<<t[rt[21]].id<<" "<<t[rt[21]].mx<<endl; rt[x]=merge(rt[x],rt[y],1,m); //cout<<" after rt[21] "<<rt[21]<<" "<<t[rt[21]].id<<" "<<t[rt[21]].mx<<endl; } } void build(){//add_edge+dfs+merge+beizeng for(reg i=2;i<=cnt;++i) add(f[i][0],i); dfs(1); for(reg j=1;j<=19;++j){ for(reg i=1;i<=cnt;++i){ f[i][j]=f[f[i][j-1]][j-1]; } } } pair<int,int>sol(int p,int l,int L,int R,int lp){ //if(lp==53) cout<<" p "<<p<<" ll "<<l<<" : "<<pos[p]<<" "<<pi[pos[p]]<<" L R "<<L<<" "<<R<<" lp "<<lp<<endl; if(pi[p]<l) return make_pair(L,0); p=pos[p]; for(reg j=19;j>=0;--j){ if(len[f[p][j]]>=l) p=f[p][j]; } //if(lp==53) cout<<" after jump "<<p<<" "<<len[p]<<" : "<<f[p][0]<<" "<<len[f[p][0]]<<" "<<rt[p]<<endl; pair<int,int>ret=query(rt[p],1,m,L,R); if(ret.first==0) ret.first=L; return ret; } void main(){ scanf("%s",s+1); //num=1; //rt[1]=1; rd(m); init(); for(reg i=1;i<=m;++i){ int now=0; scanf("%s",a+1); now=strlen(a+1); nd=1; for(reg j=1;j<=now;++j){ ins(j,a[j]-'a',i); } //cout<<" rt[2] "<<rt[2]<<" "<<t[rt[2]].id<<" "<<t[rt[2]].mx<<endl; } wrk(s); //int lll=strlen(s+1); // for(reg i=1;i<=lll;++i){ // cout<<i<<" : "<<pos[i]<<endl; // } build(); //cout<<" rt[2] "<<rt[2]<<" "<<t[rt[2]].id<<" "<<t[rt[2]].mx<<endl; int q; rd(q); int l,r,pl,pr; int o=0; while(q--){ rd(l);rd(r);rd(pl);rd(pr); pair<int,int>ans=sol(pr,pr-pl+1,l,r,++o); printf("%d %d\n",ans.first,ans.second); } } }sam; int main(){ sam.main(); return 0; } } signed main(){ Miracle::main(); return 0; } /* Author: *Miracle* Date: 2018/12/31 10:57:37 */
总结:
思路还是简单自然的
要多串的子串匹配,就广义SAM
询问和S子串有关,就先跑一下匹配
然后询问,考虑结尾位置,然后倍增,子树出现标记?线段树合并!
细节还是要注意。