【广义SAM+线段树合并】CF666E Forensic Examination

【题目】
原题地址
给定一个模式串 S S S n n n个匹配串 T i T_i Ti q q q个询问形如 ( l , r , p l , p r ) (l,r,pl,pr) (l,r,pl,pr),问 S [ p l … p r ] S[pl\dots pr] S[plpr] T l … T r T_l\dots T_r TlTr中哪个出现了最多次(可重叠),输出这个次数和是哪个串。
∣ S ∣ , q ≤ 5 × 1 0 5 , n , ∑ ∣ T ∣ ≤ 5 × 1 0 4 |S|,q\leq 5\times 10^ 5,n,\sum |T| \leq 5\times 10^4 S,q5×105,n,T5×104

【解题思路】
将所有串一起建出广义 SAM \text{SAM} SAM,每个匹配串代表一个颜色,那么每个匹配串的节点实际上就会使这种颜色的贡献 + 1 +1 +1,查询实际上就是子树查询颜色出现最大值。于是我们建出权值线段树,然后用线段树合并得到每个节点的线段树。
对于每个询问 S [ x , y ] S[x,y] S[x,y],我们找到 S S S的以 y y y为结尾的前缀在 SAM \text{SAM} SAM对应节点,然后用倍增找到 S [ x , y ] S[x,y] S[x,y]对应的节点查询即可。
时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn),这个 n n n大概是 1 0 6 10^6 106级别吧。

【参考代码】

#include
using namespace std;

const int N=11e5+10,M=N*30;
int n,m,Q,ans,ps;
int rt[N],b[N],c[N],id[N],bz[N][21];
char s[N];

int read()
{
	int ret=0;char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
	return ret;
}

struct SAM
{
	int sz,las,fa[N],mx[N],ch[N][26];
	int extend(int c)
	{
		int p,q,np,nq;
		if (ch[las][c])
	    {
	        p=las,q=ch[p][c];
	        if (mx[q]==mx[p]+1) return (las=q);
	        nq=++sz;mx[nq]=mx[p]+1;
	        memcpy(ch[nq],ch[q],sizeof(ch[q]));
	        fa[nq]=fa[q];fa[q]=nq;
	        while(p && ch[p][c]==q) ch[p][c]=nq,p=fa[p];
	        return (las=nq);
	    }
		p=las;las=np=++sz;mx[np]=mx[p]+1;
		while(p && !ch[p][c]) ch[p][c]=np,p=fa[p];
		if(!p) fa[np]=1;
		else
		{
			q=ch[p][c];
			if(mx[p]+1==mx[q]) fa[np]=q;
			else
			{
				nq=++sz;mx[nq]=mx[p]+1;
				memcpy(ch[nq],ch[q],sizeof(ch[q]));
				fa[nq]=fa[q];fa[np]=fa[q]=nq;
				while(p && ch[p][c]==q) ch[p][c]=nq,p=fa[p];
			}
		}
		return las;
	}
}S;

struct Segment
{
	int sz,ls[M],rs[M],mx[M],pos[M];
	void upd(int x)
	{
		if(mx[ls[x]]>=mx[rs[x]]) mx[x]=mx[ls[x]],pos[x]=pos[ls[x]];
		else mx[x]=mx[rs[x]],pos[x]=pos[rs[x]];
	}
	int merge(int x,int y,int l,int r)
	{
		if(!x || !y) return x+y;
		int d=++sz,mid=(l+r)>>1;
		if(l==r){mx[d]=mx[x]+mx[y];pos[d]=l;return d;}
		ls[d]=merge(ls[x],ls[y],l,mid);
		rs[d]=merge(rs[x],rs[y],mid+1,r);
		upd(d); return d;
	}
	void insert(int &x,int l,int r,int p)
	{
		if(!x) x=++sz;
		if(l==r){++mx[x];pos[x]=l;return;}
		int mid=(l+r)>>1;
		if(p<=mid) insert(ls[x],l,mid,p);
		else insert(rs[x],mid+1,r,p);
		upd(x);
	}
	void query(int x,int l,int r,int L,int R)
	{
		if(L<=l && r<=R) {if(mx[x]>ans)ans=mx[x],ps=pos[x];return;}
		int mid=(l+r)>>1;
		if(L<=mid) query(ls[x],l,mid,L,R);
		if(R>mid) query(rs[x],mid+1,r,L,R);
	}
	void init()
	{
		for(int i=1;i<=S.sz;++i) bz[i][0]=S.fa[i];
		for(int j=1;j<=20;++j) for(int i=1;i<=S.sz;++i) 
			bz[i][j]=bz[bz[i][j-1]][j-1];
		for(int i=1;i<=S.sz;++i) b[S.mx[i]]++;
		for(int i=1;i<=S.sz;++i) b[i]+=b[i-1];
		for(int i=S.sz;i;--i) c[b[S.mx[i]]--]=i;
		for(int i=S.sz;i>1;--i) rt[S.fa[c[i]]]=merge(rt[S.fa[c[i]]],rt[c[i]],1,m);
	}
}tr;

int main()
{
#ifndef ONLINE_JUDGE
	freopen("CF666E.in","r",stdin);
	freopen("CF666E.out","w",stdout);
#endif
	S.sz=S.las=1;
	scanf("%s",s+1);n=strlen(s+1);
	for(int i=1;i<=n;++i) id[i]=S.extend(s[i]-'a');
	
	m=read();
	for(int i=1,l;i<=m;++i)
	{
		scanf("%s",s+1);l=strlen(s+1);S.las=1;
		for(int j=1,x;j<=l;++j) x=S.extend(s[j]-'a'),tr.insert(rt[x],1,m,i);
	}
	//for(int i=1;i<=S.sz;++i) printf("%d\n",S.fa[i]);
	tr.init();
	
	Q=read();
	while(Q--)
	{
		int l=read(),r=read(),x=read(),y=read(),now=id[y];
		for(int i=20;~i;--i) if(S.mx[bz[now][i]]>=y-x+1) now=bz[now][i];
		ans=0;ps=l;tr.query(rt[now],1,m,l,r);
		printf("%d %d\n",ps,ans);
	}

	return 0;
}

【总结】
论广义 SAM \text{SAM} SAM的正确写法。

你可能感兴趣的:(字符串-SAM,数据结构-线段树)