【题解】CF666E Forensic Examination

CF666E Forensic Examination

\(\text{Solution:}\)

一个基本的思路是考虑如何对 SAM 上的点维护其子树内在每个串的出现次数。

那这个东西是显然的线段树合并,打标记合并即可。

接下来考虑如何迅速找到一个串的 parent 树上的对应点。那对 \(S\) 我们可以考虑把 \(S\) 的前缀点对应在 SAM 上的点编号预处理好,知道子串长度后从匹配到的点向上倍增跳就可以做到一个 $\log $ 定位子串了。

那想到这里这题就已经做完了。

观察一下细节:如果不把 \(S\) 插入到 SAM 就直接匹配确定点会有很多特殊情况判断,比如匹配长度不足等等。我们可以考虑把 \(S\) 插入到 SAM 里面再找对应节点,就没有那么多问题了。

还要注意线段树合并的时候维护两个值,最大值和位置。合并的时候只有叶子节点继承信息,其他节点需要 pushup 来维护。位置要尽量靠左维护。

#include<bits/stdc++.h>
using namespace std;
const int SN=2e6+10;
const int N=4e6+10;
inline int Max(int x,int y){return x>y?x:y;}
inline int Min(int x,int y){return x<y?x:y;}
const int inf=(1<<30);
typedef pair<int,int> pr;
#define fi first
#define se second
#define mp make_pair
inline pr Max(pr x,pr y){
	if(x.se==y.se)return x.fi<y.fi?x:y;
	return x.se>y.se?x:y;
}
int qy,m,pos[SN];
namespace SGT{
	int ls[N],rs[N],node;
	pr mxpos[N];
	inline void pushup(const int &x){mxpos[x]=Max(mxpos[ls[x]],mxpos[rs[x]]);}
	void change(int &x,const int &L,const int &R,const int &pos){
		if(!x)x=++node;
		if(L==R){
			mxpos[x].fi=L;
			mxpos[x].se++;
			return;
		}
		int mid=(L+R)>>1;
		if(pos<=mid)change(ls[x],L,mid,pos);
		else change(rs[x],mid+1,R,pos);
		pushup(x);
	}
	int merge(const int &x,const int &y,const int &L,const int &R){
		if(!x||!y)return x|y;
		int p=++node;
		int mid=(L+R)>>1;
		if(L==R){
			mxpos[p].fi=L;
			mxpos[p].se=mxpos[x].se+mxpos[y].se;
			return p;
		}
		ls[p]=merge(ls[x],ls[y],L,mid);
		rs[p]=merge(rs[x],rs[y],mid+1,R);
		pushup(p);return p;
	}
	pr query(const int &x,const int &L,const int &R,const int &l,const int &r){
		if(L>=l&&R<=r){return mxpos[x];}
		int mid=(L+R)>>1;
		pr res=mp(-1,0);
		if(l<=mid)res=query(ls[x],L,mid,l,r);
		if(mid<r)res=Max(res,query(rs[x],mid+1,R,l,r));
		return res;
	}
}
using namespace SGT;
namespace SAM{
	int len[SN],pa[SN],ch[SN][26],rt[SN],tot=1,last=1,col[SN],f[SN][23];
	vector<int>G[SN];
	void insert(const int &c,const int &cl){
		int p=last;int np=++tot;last=tot;
		len[np]=len[p]+1;
		for(;p&&!ch[p][c];p=pa[p])ch[p][c]=np;
		if(!p)pa[np]=1;
		else{
			int q=ch[p][c];
			if(len[q]==len[p]+1)pa[np]=q;
			else{
				int nq=++tot;
				len[nq]=len[p]+1;
				memcpy(ch[nq],ch[q],sizeof ch[q]);
				pa[nq]=pa[q];pa[q]=pa[np]=nq;
				for(;p&&ch[p][c]==q;p=pa[p])ch[p][c]=nq;
			}
		}
	}
	void dfs(int x){
		for(auto v:G[x]){
			f[v][0]=x;
			for(int i=1;i<23;++i)f[v][i]=f[f[v][i-1]][i-1];
			dfs(v);
			rt[x]=merge(rt[x],rt[v],1,m);
		}
	}
	void Build(){
		for(int i=2;i<=tot;++i)G[pa[i]].push_back(i);
		dfs(1);
	}
}
using namespace SAM;
char s[SN];
string t[SN];
void solve(int sl,int sr,int tl,int tr){
	int spos=pos[sr];
	if(spos<=1){
		printf("%d 0\n",tl);
		return;
	}
	int lens=sr-sl+1;
	int now=spos;
	for(int i=22;~i;--i){if(now&&len[f[now][i]]>=lens)now=f[now][i];}
	if(!now||now==1||len[now]<lens){
		printf("%d 0\n",tl);
		return;
	}
	pr Ans=query(rt[now],1,m,tl,tr);
	if(!Ans.se)Ans.fi=tl;
	printf("%d %d\n",Ans.fi,Ans.se);
}
int main(){
	scanf("%s",s+1);
	scanf("%d",&m);
	for(int i=1;i<=m;++i){
		last=1;
		cin>>t[i];
		int tlen=t[i].size();
		for(int j=1;j<=tlen;++j){
			int v=t[i][j-1]-'a';
			insert(v,i);
		}
	}
	int slen=strlen(s+1);
	last=1;
	for(int i=1;i<=slen;++i){
		int v=s[i]-'a';
		insert(v,0);
	}
	int now=1;
	for(int i=1;i<=slen;++i){
		int v=s[i]-'a';
		now=ch[now][v];pos[i]=now;
	}
	for(int i=1;i<=m;++i){
		int tlen=t[i].size();
		int now=1;
		for(int j=0;j<tlen;++j){
			int v=t[i][j]-'a';
			now=ch[now][v];
			change(rt[now],1,m,i);
		}
	}
	Build();
	scanf("%d",&qy);
	while(qy--){
		int sl,sr,tl,tr;
		scanf("%d%d%d%d",&tl,&tr,&sl,&sr);
		solve(sl,sr,tl,tr);
	}
	return 0;
}
上一篇:对于指针的了解


下一篇:【题解】[BJOI2020] 封印