CF666E Forensic Examination(后缀自动机,可持久化线段树合并)

2021/11/4 23:09:59

本文主要是介绍CF666E Forensic Examination(后缀自动机,可持久化线段树合并),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

给你一个串\(S\),以及一个字符串数组\(T_{1,2,...m}\),\(q\)次询问,每次问\(S\)的子串\(S[p_l,...p_r]\)在\(T_{l...r}\)中的哪个串的出现次数最多,并输出出现次数。

做法:

对串\(S\)和数组\(T\)建立后缀自动机。

在后缀自动机上找到\(S[l,r]\)这个子串对应的节点u,这是一个经典操作。

数组\(T\)内每个字符串都可以视作一种颜色。

现在问题转变为,每次询问一个节点,节点的link树子树内部哪种颜色出现次数最多,这个次数是多少。

这里可以线段树合并,或者树上启发式合并来做。

这里选择用线段树合并的做法。

时间复杂度\(O(nlogn)\)。

#include<bits/stdc++.h>
using namespace std;
const int maxn=6e5+10;
int m,q;
int n,len[maxn<<1],link[maxn<<1],nxt[maxn<<1][26],tot=1,lst=1;
string s;
string t[maxn];
void sam_extend (char c) {
	int cur=++tot,p=lst;
	len[cur]=len[lst]+1;
	while (p&&!nxt[p][c-'a']) {
		nxt[p][c-'a']=cur;
		p=link[p];
	}
	if (!p) link[cur]=1;
	else {
		int q=nxt[p][c-'a'];
		if (len[p]+1==len[q]) {
			link[cur]=q;
		}
		else {
			int clone=++tot;
			len[clone]=len[p]+1;
			for (int i=0;i<26;i++) {
				nxt[clone][i]=nxt[q][i];
			}
			link[clone]=link[q];
			while (p&&nxt[p][c-'a']==q) {
				nxt[p][c-'a']=clone;
				p=link[p];
			}
			link[q]=link[cur]=clone;
		}
	}
	lst=cur;
}
const int M=maxn*40;
int c[M],id[M],lson[M],rson[M],tol,T[maxn<<1];
int up (int u,int l,int r,int p,int v) {
	if (!u) u=++tol;
	if (l==r) {
		c[u]+=v;
		id[u]=l;
		return u;
	}
	int mid=(l+r)>>1;
	if (p<=mid) lson[u]=up(lson[u],l,mid,p,v);
	if (p>mid) rson[u]=up(rson[u],mid+1,r,p,v);
	c[u]=max(c[lson[u]],c[rson[u]]);
	if (c[rson[u]]==c[u]) id[u]=id[rson[u]];
	if (c[lson[u]]==c[u]) id[u]=id[lson[u]];
	return u;
}
int merge (int x,int y,int l,int r) {
	if (!x||!y) return x+y;
	int u=++tol;
	if (l==r) {
		c[u]=c[x]+c[y];
		id[u]=l;
		return u;
	}
	int mid=(l+r)>>1;
	lson[u]=merge(lson[x],lson[y],l,mid);
	rson[u]=merge(rson[x],rson[y],mid+1,r);
	c[u]=max(c[lson[u]],c[rson[u]]);
	if (c[rson[u]]==c[u]) id[u]=id[rson[u]];
	if (c[lson[u]]==c[u]) id[u]=id[lson[u]];
	return u;
}
pair<int,int> query (int u,int l,int r,int L,int R) {
	//printf("%d %d %d %d %d\n",u,l,r,L,R);
	if (L>R) return make_pair(0,0);
	if (l>=L&&r<=R) {
		return make_pair(c[u],id[u]);
	}
	int mid=(l+r)>>1;
	pair<int,int> ans=make_pair(0,0);
	if (L<=mid) ans=query(lson[u],l,mid,L,R);
	if (R>mid) {
		pair<int,int> tt=query(rson[u],mid+1,r,L,R);
		if (tt.first>ans.first) {
			ans.first=tt.first;
			ans.second=tt.second;
		}
	}
	return ans;
}
vector<int> g[maxn<<1];
int father[25][maxn<<1];
void dfs (int u) {
	for (int i=1;i<=20;i++) father[i][u]=father[i-1][father[i-1][u]];
	for (int v:g[u]) {
		father[0][v]=u;
		dfs(v);
		T[u]=merge(T[u],T[v],1,m);
	}
}
int ed[maxn];
int main () {
	ios::sync_with_stdio(false);
	cin>>s;
	for (int i=0;i<s.size();i++) {
		sam_extend(s[i]);
		ed[i+1]=lst;
	}
	cin>>m;
	for (int i=1;i<=m;i++) {
		cin>>t[i];
		lst=1;
		for (char ch:t[i]) sam_extend(ch),T[lst]=up(T[lst],1,m,i,1);
	}
	for (int i=2;i<=tot;i++) g[link[i]].push_back(i);
	dfs(1);
	cin>>q;
	while (q--) {
		int A,B,C,D;
		cin>>C>>D>>A>>B;
		int u=ed[B];
		for (int i=20;i>=0;i--) {
			if (father[i][u]&&len[father[i][u]]>=B-A+1) {
				u=father[i][u];
			}
		}
		pair<int,int> ans=query(T[u],1,m,C,D);
		if (ans.first==0) {
			ans={0,C};	
		}
		cout<<ans.second<<" "<<ans.first<<'\n';
	}
}


这篇关于CF666E Forensic Examination(后缀自动机,可持久化线段树合并)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程