poj2758/bzoj2258 文本校对 后缀数组+RMQ

题目大意

对于一个字符串进行以下两个操作:
I:插入一个字符到现在字符串中一个字符的前面。该操作不超过2000个
Q:询问原来的字符串中以第a个字符和第b个字符,从它们现在所处的位置开始的最长公共前缀

题目分析

维护三个数组: p o s i pos_i posi:原串中第i个字符现在所处的位置, o p s i ops_i opsi:现在串中第i个字符原来所处的位置(如果是插入进来的字符,则 o p s i = − 1 ops_i=-1 opsi=1), d i s i dis_i disi原串中的第i个字符后面第一个新插入的字符离它的距离。
对原串预处理好后缀数组和Height数组。
对于插入操作,则是对以上三个数组和字符串数组进行维护。
对于询问操作:
首先利用RMQ求得原串中那两个后缀的最长公共前缀长度LEN:假设是后缀as和后缀bs,则答案是Height中区间中[as+1,bs]的最小值。
假如LEN小于 d i s a s dis_{as} disas d i s b s dis_{bs} disbs,可直接获得答案。
否则,从下一个新插入的字符开始依次暴力匹配,失配时可以获得答案,或是匹配到两个原串中的字符,则可以递归求解。
题目不是很难思考,但是容易混乱原串当前串的关系(至少本蒟蒻是因此调了一晚上最后重构代码才A掉的)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=60005,inf=0x3f3f3f3f;
char s[N];int n,nlen,m,q;
int a[N],SA[N],rk[N],y[N],T[N],Hei[N];
int f[N][18],bin[N],pos[N],ops[N],dis[N];
int cmp(int i,int j,int num) {return y[i]==y[j]&&y[i+num]==y[j+num];}
void Rsort() {
	for(int i=0;i<=m;++i) T[i]=0;
	for(int i=1;i<=n;++i) ++T[rk[y[i]]];
	for(int i=1;i<=m;++i) T[i]+=T[i-1];
	for(int i=n;i>=1;--i) SA[T[rk[y[i]]]--]=y[i];
}
void getSA() {
	for(int i=1;i<=n;++i) y[i]=i,rk[i]=a[i];
	m=127,Rsort();
	for(int km=1,num=1;km<n;num+=num,m=km) {
		km=0;
		for(int i=n-num+1;i<=n;++i) y[++km]=i;
		for(int i=1;i<=n;++i) if(SA[i]>num) y[++km]=SA[i]-num;
		Rsort();for(int i=1;i<=n;++i) y[i]=rk[i];km=rk[SA[1]]=1;
		for(int i=2;i<=n;++i) rk[SA[i]]=cmp(SA[i],SA[i-1],num)?km:++km;
	}
	for(int i=1;i<=n;++i) rk[SA[i]]=i;
}
void getHei() {
	int lcp=0;
	for(int i=1;i<=n;++i) {
		if(lcp) --lcp;
		int j=SA[rk[i]-1];
		while(a[j+lcp]==a[i+lcp]&&lcp<n) ++lcp;
		Hei[rk[i]]=lcp;
	}
}
void getRMQ() {
	int kl=0;
	for(int i=2;i<=n+1;++i) {
		if((1<<(kl+1))==i) ++kl;
		bin[i]=kl;
	}
	for(int i=2;i<=n;++i) f[i][0]=Hei[i];
	for(int j=1;j<=bin[n];++j)
		for(int i=2;i+(1<<j)-1<=n;++i)
			f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
int LCP(int as,int bs) {//原串中的匹配长度[as+1,bs]最小值
	if(as==bs) return nlen-as+1;
	as=rk[as],bs=rk[bs]; if(as>bs) swap(as,bs);
	int kl=bin[bs-as];
	return min(f[as+1][kl],f[bs-(1<<kl)+1][kl]);
}
void ins(int num,int x) {
	for(int i=n;i>=x;--i) {
		a[i+1]=a[i],ops[i+1]=ops[i];
		if(ops[i]!=-1) pos[ops[i]]=i+1;
	}
	++n,a[x]=num,ops[x]=-1;
	for(int i=x-1;i>=1&&ops[i]!=-1;--i) dis[ops[i]]=x-i;
}
int query(int as,int bs) {//递归求解过程
	int k1=LCP(as,bs),k2=min(dis[as],dis[bs]);
	as=pos[as],bs=pos[bs];
	if(k1<k2) return k1;
	for(int i=k2;;++i) {
		if(a[as+i]!=a[bs+i]||!a[as+i]||!a[bs+i]) return i;
		if(ops[as+i]!=-1&&ops[bs+i]!=-1) return i+query(ops[as+i],ops[bs+i]);
	}
}
int main()
{
	char ch[10];int l,r;
	scanf("%s",s),n=nlen=strlen(s);
	for(int i=1;i<=n;++i) a[i]=s[i-1];
	getSA(),getHei(),getRMQ();
	for(int i=1;i<=n;++i) ops[i]=pos[i]=i,dis[i]=inf;
	scanf("%d",&q);
	while(q--) {
		scanf("%s",ch);
		if(ch[0]=='Q') scanf("%d%d",&l,&r),printf("%d\n",query(l,r));
		else scanf("%s %d",ch,&l),ins(ch[0],min(l,n+1));
	}
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值