【CF 666E】Forensic Examination

洛谷

题意:

给你一个串 S S S以及一个字符串数组 T [ 1.. m ] T[1..m] T[1..m] q q q次询问,每次问 S S S的子串 S [ p l . . p r ] S[pl..pr] S[pl..pr] T [ l . . r ] T[l..r] T[l..r]中的哪个串里的出现次数最多,并输出出现次数。

如有多解输出最靠前的那一个。

Sol

(一遍就写出来了 S A M SAM SAM+线段树合并+倍增 , 顿时感觉码力大增 ,一发过样例然后 A C AC AC 是真的舒服)

多串匹配 , 考虑构建广义 S A M SAM SAM

下面如果懂得广义 S A M SAM SAM的精髓就很简单了
把所有的串丢到广义 S A M SAM SAM 上 , 此时相当于所有串都彼此之间做了匹配 , 并且自动机上的一个节点就包含了所有串的匹配信息!!
考虑与 S S S 的子串匹配 , 我们只需要找到 S A M SAM SAM 上识别该子串的节点 , 取出该节点的其他串的 ∣ e n d p o s ∣ |endpos| endpos 取一个 M a x Max Max 就是所有串的与 S S S 该子串匹配次数的 M a x Max Max ,但是题中要求出的是一段区间的串 , 那么线段树来维护就可以了 , 一个结点的线段树维护子树内所有串 ∣ e n d p o s ∣ |endpos| endpos集合的大小 , 询问的时候得到 [ L , R ] [L,R] [L,R]的最大值就行了!! 当然要用线段树合并来求 ∣ e n d p o s ∣ |endpos| endpos并维护最大值。
至于如何找到能 S S S 子串的节点 , 我们在建立 S A M SAM SAM 的时候用的是增量法 , 那么我们很容易确定一个前缀的位置 , 然后子串就是前缀的后缀 , 直接找到以子串右端点结尾的前缀的位置,树上倍增就能找到该子串了,因为从 p a r e n t parent parent树往上都是自己的后缀。所以这道看似不可做的题就用广义 S A M SAM SAM 得到了非常完美的解决。

代码:

#include<bits/stdc++.h>
using namespace std;
#define Set(a,b) memset(a,b,sizeof(a))
#define Copy(a,b) memcpy(a,b,sizeof(a))
const int N=5e5+10;
const int M=5e4+10;
const int MAXN=2e6+10;
typedef pair<int,int> Pr;
char S[N];int pos[N];
int m,q;
namespace Segment{
	const int MAXM=1e7+10;
	int rt[MAXN],cnt=0;
	struct node{int Max,ls,rs,id;}T[MAXM];
	void Modify(int &u,int l,int r,int pos,int x){
		if(!u) u=++cnt;
		T[u].Max=x,T[u].id=pos;
		if(l==r) return;int mid=l+r>>1;
		if(mid>=pos) Modify(T[u].ls,l,mid,pos,x);
		else Modify(T[u].rs,mid+1,r,pos,x);
		T[u].Max=max(T[T[u].ls].Max,T[T[u].rs].Max);
		return;
	}
	int Merge(int u,int v,int l,int r){
		if(!u||!v) return u|v;int p=++cnt;T[p].ls=T[p].rs=0;
		if(l==r) {T[p].Max=T[u].Max+T[v].Max;T[p].id=l;return p;}
		int mid=l+r>>1;
		T[p].ls=Merge(T[u].ls,T[v].ls,l,mid);
		T[p].rs=Merge(T[u].rs,T[v].rs,mid+1,r);
		if(T[T[p].ls].Max>=T[T[p].rs].Max) T[p].Max=T[T[p].ls].Max,T[p].id=T[T[p].ls].id;
		else T[p].Max=T[T[p].rs].Max,T[p].id=T[T[p].rs].id;
		return p;
	}
	Pr Query(int u,int l,int r,int L,int R){
		if(!u) return Pr(0,0);
		if(l>=L&&r<=R) return Pr(T[u].Max,T[u].id);
		int mid=l+r>>1;
		if(mid>=R) return Query(T[u].ls,l,mid,L,R);
		if(mid< L) return Query(T[u].rs,mid+1,r,L,R);
		Pr LA=Query(T[u].ls,l,mid,L,mid);
		Pr RA=Query(T[u].rs,mid+1,r,mid+1,R);
		if(LA.first>=RA.first) return LA;return RA;
	}
}using Segment::rt;using Segment::Modify;using Segment::Merge;

namespace SAM{
	int son[MAXN][26],fa[MAXN],len[MAXN],cnt=0;int now,last;
	struct edge{int to,next;}a[MAXN];int cur=0;
	int head[MAXN];
	inline void add(int x,int y){a[++cur]=(edge){y,head[x]};head[x]=cur;}
	inline int extend(int last,int c){
		int u=last,p=++cnt;len[p]=len[last]+1;
		while((~u)&&!son[u][c]) son[u][c]=p,u=fa[u];
		if(~u){
			int v=son[u][c];
			if(len[v]==len[u]+1) fa[p]=v;
			else {
				int q=++cnt;len[q]=len[u]+1;Copy(son[q],son[v]);
				fa[q]=fa[v];fa[v]=fa[p]=q;
				while((~u)&&son[u][c]==v) son[u][c]=q,u=fa[u];
			}
		}
		return p;
	}
	inline void Insert(int len,int id){
		last=0;fa[0]=-1;
		for(int i=1;i<=len;++i) {
			last=extend(last,S[i]-'a');
			if(!id) pos[i]=last;else Modify(rt[last],1,m,id,1);
		}
		return;
	}
	int Fa[22][MAXN];
	void Dfs(int u){
		Fa[0][u]=fa[u];
		for(int v,i=head[u];i;i=a[i].next) {v=a[i].to;Dfs(v);rt[u]=Merge(rt[u],rt[v],1,m);}
		return;
	}
	inline void Build(){
		for(int i=1;i<=cnt;++i) add(fa[i],i);Dfs(0);
		for(int i=1;i<=21;++i) for(int u=1;u<=cnt;++u)	Fa[i][u]=Fa[i-1][Fa[i-1][u]];
		return;
	}
	inline int Find(int u,int Len) {for(int i=21;~i;--i) if(Fa[i][u]&&len[Fa[i][u]]>=Len) u=Fa[i][u];return u;}
	
}using SAM::Insert;using SAM::Build;
int main()
{
	scanf("%s",S+1);
	int len=strlen(S+1);
	Insert(len,0);
	scanf("%d",&m);
	for(int i=1;i<=m;++i) {scanf("%s",S+1);len=strlen(S+1);Insert(len,i);}
	Build();
	scanf("%d",&q);
	for(int i=1;i<=q;++i) {
		int l,r,pl,pr;
		scanf("%d %d %d %d",&l,&r,&pl,&pr);
		int p=pos[pr];len=pr-pl+1;
		int u=SAM::Find(p,len);
		Pr ans=Segment::Query(rt[u],1,m,l,r);
		if(!ans.first) ans.second=l;
		printf("%d %d\n",ans.second,ans.first);
	}
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值