CF666E Forensic Examination
\(CF666E\ \ Forensic\ Examination\)
题意
给定母串 \(s\) 和 \(m\) 个文本串 \(t_1,t_2,\dots t_m\),给出 \(q\) 次询问,每次 \(st,ed,ql,qr\),回答从 \(t_{st}-t_{ed}\) 中出现子串 \(s[ql,qr]\) 次数最多的文本串编号和出现次数。
前置知识
广义后缀自动机 \((GSAM)\)
动态开点线段树+线段树合并
思路分析
对于 \(m\) 个串我们显然要扔进 \(GSAM\) 里,然后我们维护每个节点的 \(siz\),\(siz[i][id]\) 为节点 \(i\) 在串 \(id\) 上的 \(endpos\) 大小。我们可以先做一道水题找相同字符[P3181]熟悉 \(siz\)
- \(P3181\) 我们考虑使用 \(siz[i][1]\) 和 \(siz[i][2]\) 维护在第一个串和第二个串上的 \(siz\),然后和单串 \(SAM\) 一样,在 \(parent\ tree\) 上进行子树求和维护 \(siz\).最后答案为 $ \sum siz[i][1]\times siz[i][2]\times (len[i]-len[link[i]]) $
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define read read()
#define pt puts("")
inline int read
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
return f*x;
}
void write(ll x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
return;
}
const int N = 5e5+10;
char s[N];
ll siz[N<<1][3];
vector<int >son[N<<1];
ll ans;
struct G_Suffix_Auto_Mation{
int tot;int last;
int link[N<<1],len[N<<1];
int ch[N<<1][27];
void insert(int c,int id)
{
int p=last;
if(ch[p][c]){
int q=ch[p][c];
if(len[p]+1==len[q]){
siz[q][id]=1;
last=q;return;
}
else{
int copy=++tot;
len[copy]=len[p]+1;
link[copy]=link[q];
for(int i=1;i<=26;i++) ch[copy][i]=ch[q][i];
while(p!=-1&&ch[p][c]==q){
ch[p][c]=copy;
p=link[p];
}
link[q]=copy;
siz[copy][id]=1;
last=copy;
return;
}
}
int cur=++tot;
len[cur]=len[p]+1;
while(p!=-1&&!ch[p][c]){
ch[p][c]=cur;
p=link[p];
}
if(p==-1) link[cur]=0;
else{
int q=ch[p][c];
if(len[p]+1==len[q]) link[cur]=q;
else{
int copy=++tot;
len[copy]=len[p]+1;
link[copy]=link[q];
for(int i=1;i<=26;i++) ch[copy][i]=ch[q][i];
while(p!=-1&&ch[p][c]==q){
ch[p][c]=copy;
p=link[p];
}
link[cur]=link[q]=copy;
}
}
siz[cur][id]=1;
last=cur;
return;
}
// void out(int x){
// for(int i=1;i<=26;i++) if(ch[x][i]) cout<<x<<' '<<ch[x][i]<<' ',putchar(i+'a'-1),pt,out(ch[x][i]);
// }
void makeparent()
{
for(int i=1;i<=tot;i++){
son[link[i]].push_back(i);
}
}
void dfs(int x){
for(int y:son[x]){
dfs(y);
siz[x][1]+=siz[y][1];
siz[x][2]+=siz[y][2];
}
}
void solve(){
for(int i=1;i<=tot;i++)
ans+=siz[i][1]*siz[i][2]*(len[i]-len[link[i]]);
write(ans);
}
}gsam;
signed main()
{
gsam.link[0]=-1;
scanf(" %s",s+1);int m=strlen(s+1);
for(int i=1;i<=m;++i) gsam.insert(s[i]-'a'+1,1);
gsam.last=0;
scanf(" %s",s+1);m=strlen(s+1);
for(int i=1;i<=m;++i) gsam.insert(s[i]-'a'+1,2);
gsam.makeparent();
gsam.dfs(0);
gsam.solve();
return 0;
}
好那么回到这个题,它有 \(5\times 10^4\) 个串,我们当然不能和上文一样维护一个二维数组 \(siz\),时间和空间都不允许,那么我们考虑将 \(siz\) 转到动态开点线段树上去并通过线段树合并维护 \(siz\) 即可。
具体地,我们先建好文本串的 \(GSAM\) ,每个节点(显然对应一个等价类)开一棵 \([1,m]\) 线段树,最后把母串塞进去,同时记录每一位对应的节点,这样方便查询。然后遍历 \(parent\ tree\),将子树合并(这相当于子树求和)。预处理好后开始查询,查询包含 \(s[ql,qr]\) 段对应的等价类,这可以通过从 \(qr\) 倍增上跳实现。最后查询该节点 \([st,ed]\) 中最大值即可。
\(AC\ \ Code\)
#include<bits/stdc++.h>
using namespace std;
#define read read()
#define pt puts("")
inline int read
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
return f*x;
}
void write(int x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
return;
}
const int N = 5e5+10;
const int M = 5e4+10;
#define NM (N+M)<<1
char s[N],t[M];
int m;int n;
int last;
int pos[NM];
int fa[NM][21],dep[NM];
vector<int >son[NM];
int root[NM];
struct SPX{
int num,id;
SPX(int Num=0,int Id=0){num=0,id=0;}
bool operator > (const SPX &spx)const{return num==spx.num?(id<spx.id):(num>spx.num);}
};
SPX MAX(SPX a,SPX b){
if(a.num==b.num) return (a.id<b.id?a:b);
return (a.num>b.num?a:b);
}
namespace Segment_Tree{
int sum;
struct Seg{
int ls,rs;
SPX ans;
#define ls(i) tr[i].ls
#define rs(i) tr[i].rs
#define ans(i) tr[i].ans
}tr[NM<<1];
void pushup(int i){
ans(i)=MAX(ans(ls(i)),ans(rs(i)));
}
void join(int &i,int l,int r,int x)//动态开点线段树
{
if(!i) i=++sum;
if(l==r){
++ans(i).num;
ans(i).id=l;
return;
}
int mid=(l+r)>>1;
if(x<=mid) join(ls(i),l,mid,x);
else join(rs(i),mid+1,r,x);
pushup(i);
return;
}
int merge(int x,int y,int l,int r){//线段树合并板子
if(!x||!y) return x+y;
int t=++sum;
if(l==r){
tr[t]=tr[x];ans(t).num=ans(x).num+ans(y).num;
return t;
}
int mid=(l+r)>>1;
ls(t)=merge(ls(x),ls(y),l,mid);
rs(t)=merge(rs(x),rs(y),mid+1,r);
pushup(t);
return t;
}
SPX ask(int i,int ql,int qr,int l,int r){
if(!i) return SPX(0,m+1);
if(ql<=l&&r<=qr) return ans(i);
int mid=(l+r)>>1;
SPX res=(0,m+1);
if(ql<=mid) res=MAX(res,ask(ls(i),ql,qr,l,mid));
if(qr>mid) res=MAX(res,ask(rs(i),ql,qr,mid+1,r));
return res;
}
}using namespace Segment_Tree;
void dfs(int x){
for(int j=1;(1<<j)<=dep[x];j++) fa[x][j]=fa[fa[x][j-1]][j-1];
for(int y:son[x]){
dep[y]=dep[x]+1;fa[y][0]=x;
dfs(y);
root[x]=merge(root[x],root[y],1,m);//子树"求和"
}
}
struct G_Suffix_Auto_mation{
int tot;
int link[NM],len[NM];
int ch[NM][27];
int insert(int c,int id)//dfs在线建GSAM板子
{
int p=last;
if(ch[p][c]){
int q=ch[p][c];
if(len[p]+1==len[q]){
if(id) join(root[q],1,m,id);
return q;
}
else{
int copy=++tot;
len[copy]=len[p]+1;
link[copy]=link[q];
for(int i=1;i<=26;i++) ch[copy][i]=ch[q][i];
while(p!=-1&&ch[p][c]==q){
ch[p][c]=copy;
p=link[p];
}
link[q]=copy;
if(id) join(root[copy],1,m,id);
return copy;
}
}
int cur=++tot;
len[cur]=len[p]+1;
while(p!=-1&&!ch[p][c]){
ch[p][c]=cur;
p=link[p];
}
if(p==-1) link[cur]=0;
else{
int q=ch[p][c];
if(len[p]+1==len[q]) link[cur]=q;
else{
int copy=++tot;
len[copy]=len[p]+1;
link[copy]=link[q];
for(int i=1;i<=26;i++) ch[copy][i]=ch[q][i];
while(p!=-1&&ch[p][c]==q){
ch[p][c]=copy;
p=link[p];
}
link[cur]=link[q]=copy;
}
}
if(id) join(root[cur],1,m,id);
return cur;
}
void makeparent(){
for(int i=1;i<=tot;i++) son[link[i]].push_back(i);//建parent tree
dep[0]=1;dfs(0);
}
int find(int u,int qlen){
u=pos[u];
for(int i=20;i>=0;i--)//倍增查找
if(fa[u][i] && len[fa[u][i]]>=qlen) u=fa[u][i];
return u;
}
void solve()
{
int st,ed,ql,qr;
st=read,ed=read,ql=read,qr=read;
int rt=root[find(qr,qr-ql+1)];//查询等价类
SPX spx=ask(rt,st,ed,1,m);
if(!spx.num) spx.id=st;
write(spx.id);putchar(' ');write(spx.num);pt;
}
}gsam;
signed main()
{
gsam.link[0]=-1;
scanf(" %s",s+1);n=strlen(s+1);
m=read;
for(int i=1;i<=m;i++){
scanf(" %s",t+1);last=0;
for(int j=1;t[j];j++) last=gsam.insert(t[j]-'a'+1,i);
}
last=0;
for(int i=1;i<=n;i++) pos[i]=last=gsam.insert(s[i]-'a'+1,0);
gsam.makeparent();
int T;T=read;while(T--) gsam.solve();
return 0;
}