洛谷P7361:拜神(SA、二分、主席树、启发式合并)

解析

很好的一道SA的题。(觉得完全可以评黑了啊qwq)
我一开始拿SAM和线段树硬做,不断修正最后发现自己无法在可接受复杂度内解决的问题,直接GG…
垃圾数据还骗到了50分
所以写一道题之前还是要先想仔细了,确定整个流程没有锅再写吧,尽量避免写一半发现不对再修正甚至直接假掉的情况,还能使实现时更加系统简洁。

考虑SA。
为什么搜SAM的tag结果全是拿SA做的啊。

答案显然具有单调性,考虑check二分的答案 L L L 是否合法。
合法的情况可以等价抽象为:区间内存在两个后缀 p , q p,q p,q,使得 l c p ( p , q ) ≤ L lcp(p,q)\le L lcp(p,q)L
用类似品酒大会的思路(这个思路似乎非常常见好用),把所有 h i ≥ L h_i\ge L hiL i , i − 1 i,i-1 i,i1 合并,最后就是看区间内是否有两个点在同一个集合内。

在线段树上存储每个结点左侧在同一集合内的结点最靠右的位置,问题转化为查询 [ l , r − L + 1 ] [l,r-L+1] [l,rL+1] 的最大值是否大于 L L L
这个线段树可以用主席树维护,每个并查集集合维护一个 set,启发式合并暴力查找前驱后缀即可。

时空复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define ok debug("OK\n")
inline ll read(){
	ll x(0),f(1);char c=getchar();
	while(!isdigit(c)){if(c=='-') f=-1;c=getchar();}
	while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	return x*f;
}
const int N=1e5+100;
const int M=1e6+100;
const int mod=1e9;

int n,m;
char s[N];

int sa[N],rk[N],id[N],oldrk[N],cnt[N];
int h[N];
void SA(){
	int m=300;
	//printf("%s\n",s+1);
	for(int i=1;i<=n;i++) ++cnt[rk[i]=s[i]];
	for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
	for(int i=n;i>=1;i--) sa[cnt[rk[i]]--]=i;
	for(int w=1;m!=n;w<<=1){
		int p=0;
		for(int i=n;i>n-w;i--) id[++p]=i;
		for(int i=1;i<=n;i++){
			if(sa[i]>w) id[++p]=sa[i]-w;
		}
		memset(cnt,0,sizeof(cnt));
		memcpy(oldrk,rk,sizeof(rk));
		for(int i=1;i<=n;i++) ++cnt[rk[id[i]]];
		for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
		for(int i=n;i>=1;i--) sa[cnt[rk[id[i]]]--]=id[i];
		m=0;
		for(int i=1;i<=n;i++){
			if(oldrk[sa[i]]==oldrk[sa[i-1]]&&
			oldrk[sa[i]+w]==oldrk[sa[i-1]+w]) rk[sa[i]]=m;
			else rk[sa[i]]=++m;
		}
	}
	for(int i=1,y=0;i<=n;i++){
		if(y) --y;
		while(s[i+y]==s[sa[rk[i]-1]+y]) ++y;
		h[rk[i]]=y;
	}
	//for(int i=1;i<=n;i++) printf("i=%d %s sa=%d h=%d\n",i,s+sa[i],sa[i],h[i]);
	return;
}

struct tree{
	int ls,rs,mx;
};
const int C=100;
struct Sefment_Tree{
	tree tr[N*C];
	int tot;
	#define mid ((l+r)>>1)
	inline int copy(int x){
		tr[++tot]=tr[x];
		assert(tot<N*C);
		return tot;
	}
	inline void pushup(int k){
		tr[k].mx=max(tr[tr[k].ls].mx,tr[tr[k].rs].mx);
		return;
	}
	int ask(int k,int l,int r,int x,int y){
		if(!k) return 0;
		if(x<=l&&r<=y) return tr[k].mx;
		int res=0;
		if(x<=mid) res=max(res,ask(tr[k].ls,l,mid,x,y));
		if(y>mid) res=max(res,ask(tr[k].rs,mid+1,r,x,y));
		return res;
	}
	void change(int &k,int l,int r,int p,int w){
		k=copy(k);
		if(l==r){
			tr[k].mx=max(tr[k].mx,w);return;
		}
		if(p<=mid) change(tr[k].ls,l,mid,p,w);
		else change(tr[k].rs,mid+1,r,p,w);
		pushup(k);
	}
	#undef mid
}t;
int rt[N];

set<int>S[N];
set<int>::iterator it;
inline int Suf(int k,int w){
	it=S[k].lower_bound(w);
	return (it==S[k].end())?0:(*it);
}
inline int Pre(int k,int w){
	it=S[k].lower_bound(w);
	if(it==S[k].begin()) return 0;
	else{
		it--;return (*it);
	}
}
int fa[N];
int find(int x){
	return fa[x]==x?x:fa[x]=find(fa[x]);
}
void merge(int x,int y,int L){
	//printf("merge: %d %d L=%d\n",x,y,L);	
	x=find(x);y=find(y);
	if(S[x].size()>S[y].size()) swap(x,y);
	//printf("  x=%d y=%d\n",x,y);
	for(int now:S[x]){
		int pre=Pre(y,now),suf=Suf(y,now);
		if(pre) t.change(rt[L],1,n,now,pre);
		if(suf) t.change(rt[L],1,n,suf,now);
		//printf("  ins:now=%d pre=%d suf=%d\n",now,pre,suf);
	}
	for(int now:S[x]){
		S[y].insert(now);
	}
	S[x].clear();
	fa[x]=y;
	return;
}

struct node{
	int h,id;
	bool operator < (const node o)const{return h>o.h;}
}p[N];

signed main(){
	#ifndef ONLINE_JUDGE
	//freopen("a.in","w",stdin);
	//freopen("a.out","w",stdin);
	#endif
	n=read();m=read();//printf("%d\n",n);
	scanf(" %s",s+1);
	SA();
	int num(0);
	for(int i=1;i<=n;i++){
		fa[i]=i;S[i].insert(i);
		if(i>1) p[++num]=(node){h[i],i};
	}
	sort(p+1,p+1+num);
	int pl=1;
	for(int i=n;i>=0;i--){
		rt[i]=rt[i+1];
		while(pl<=num&&p[pl].h==i){
			if(p[pl].id>1){
				int x=sa[p[pl].id],y=sa[p[pl].id-1];
				merge(x,y,i);
			}
			++pl;
		}
	}
	
	for(int i=1;i<=m;i++){
		int l=read(),r=read(),st=0,ed=n;
		while(st<ed){
			int mid=(st+ed+1)>>1;
			if(t.ask(rt[mid],1,n,l,r-mid+1)>=l) st=mid;
			else ed=mid-1;
		}
		printf("%d\n",st);
	}
	return 0;
}
/*
8 1
aabaabba
4 5

*/




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值