[洛谷P6640] BJOI2020 封印

题目链接

洛谷

解析

我们先给 \(T\) 串建一个后缀自动机,然后求出 \(S\) 中以每一个位置结尾的最长公共子串(设其长度为 \(len_i\))。这样问题就转变为对于区间 \([L,R]\) 求最大的 \(min(len_i,i-L+1)\) 。发现这个 \(min\) 实在是不太好搞,我们转化一下,如果 \(len_i\le i-L+1\) 就可以得到 \(i-len_i+1\ge L\) 。而 \(i-len_i+1\) 是随着 \(i\) 的增大而不降的。因此我们可以通过二分找到一个位置 \(pos\) 使得 \([pos,R]\) 中的 \(i-len_i+1\) 全都不小于 \(L\) 。二分和统计答案的过程可以用线段树维护。

当然正确的写法是ST表,但是有O2

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#define N 200002
using namespace std;
struct SAM{
	int len,link,son[2];
}a[N*2];
struct SegmentTree{
	int dat,len;
}t[N*4];
char S[N],T[N];
int n,m,q,i,val[N],cnt,last;
void extend(char c)
{
	int cur=++cnt,now=last,x=(int)(c-'a');
	a[cur].len=a[now].len+1;
	while(now!=-1&&!a[now].son[x]){
		a[now].son[x]=cur;
		now=a[now].link;
	}
	if(now==-1) a[cur].link=0;
	else{
		int p=now,q=a[p].son[x];
		if(a[q].len==a[p].len+1) a[cur].link=q;
		else{
			int tmp=++cnt;
			a[tmp]=a[q];
			a[tmp].len=a[p].len+1;
			a[q].link=a[cur].link=tmp;
			while(p!=-1&&a[p].son[x]==q){
				a[p].son[x]=tmp;
				p=a[p].link;
			}
		}
	}
	last=cur;
}
void build(int p,int l,int r)
{
	if(l==r){
		t[p].len=val[l];
		t[p].dat=l-val[l]+1;
		return;
	}
	int mid=(l+r)/2;
	build(p*2,l,mid);build(p*2+1,mid+1,r);
	t[p].len=max(t[p*2].len,t[p*2+1].len);
	t[p].dat=min(t[p*2].dat,t[p*2+1].dat);
}
int ask1(int p,int l,int r,int ql,int qr)
{
	if(ql>qr) return 0;
	if(ql<=l&&r<=qr) return t[p].len;
	int mid=(l+r)/2,ans=0;
	if(ql<=mid) ans=max(ans,ask1(p*2,l,mid,ql,qr));
	if(qr>mid) ans=max(ans,ask1(p*2+1,mid+1,r,ql,qr));
	return ans;
}
int ask2(int p,int l,int r,int ql,int qr)
{
	if(ql<=l&&r<=qr) return t[p].dat;
	int mid=(l+r)/2,ans=1<<30;
	if(ql<=mid) ans=min(ans,ask2(p*2,l,mid,ql,qr));
	if(qr>mid) ans=min(ans,ask2(p*2+1,mid+1,r,ql,qr));
	return ans;
}
int main()
{
	scanf("%s%s",S+1,T+1);
	n=strlen(S+1);m=strlen(T+1);
	a[0].link=-1;
	for(i=1;i<=m;i++) extend(T[i]);
	int v=0,l=0;
	for(i=1;i<=n;i++){
		int p=(int)(S[i]-'a');
		while(v&&!a[v].son[p]) v=a[v].link,l=a[v].len;
		if(a[v].son[p]) v=a[v].son[p],l++;
		val[i]=l;
	}
	build(1,1,n);
	scanf("%d",&q);
	for(i=1;i<=q;i++){
		int L,R;
		scanf("%d%d",&L,&R);
		int l=L,r=R,mid,pos=R+1;
		while(l<=r){
			mid=(l+r)/2;
			if(ask2(1,1,n,mid,R)>=L) pos=mid,r=mid-1;
			else l=mid+1;
		}
		printf("%d\n",max(pos-L,ask1(1,1,n,pos,R)));
	}
	return 0;
}
posted @ 2021-01-27 21:51  CJlzf  阅读(72)  评论(0编辑  收藏  举报