CF666E Forensic Examination
1.CF494C Helping People2.CF1037H Security3.CF1530E Minimax4.CF955D Scissors
5.CF666E Forensic Examination
6.CF1535F String Distance\(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; }
分类:
数据结构 / 线段树
, 字符串 / SAM
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现