[bzoj2803][Poi2012]Prefixuffix——思维+复杂度分析+字符串哈希

题目大意:

对于两个串S1、S2,如果能够将S1的一个后缀移动到开头后变成S2,就称S1和S2循环相同。例如串ababba和串abbaab是循环相同的。
给出一个长度为n的串S,求满足下面条件的最大的L:

  1. L<=n/2
  2. S的L前缀和S的L后缀是循环相同的。

思路:

既然是循环同构,那么前一段和后一段一定可以表示为这样的形式:s1+s2,s2+s1。
于是我们去枚举这个断点,然后判断两边是否相同和中间相同的最大值。
判断相同可以用哈希,考虑怎么求去掉一段前后缀之后首尾相同的最大长度:
设f[i]为以i开始的最长相同的长度,假设目前相同的区间是[i,p]=[n-p+1,n-i+1],那么不难发现 [i+1,p-1]=[n-p+2,n-i+2],于是f[i+1]>=f[i]-2,即f至多减少2,于是我们直接从p-1枚举,找到最大的为止。
但是这样并没有优化复杂度,因为这要便历整个串才能找到最大的,于是我们倒着推,即每一次f至多增加2,倒着枚举只需要找到第一个满足条件的就好了,这样可以保证是最大的。
这样的复杂度是 Θ ( n ) \Theta(n) Θ(n)的,指针至多只会往后跳n次,其余的时间都在单调向前移动,即移动的长度至多为n*2。

#include<bits/stdc++.h>

#define REP(i,a,b) for(int i=a,i##_end_=b;i<=i##_end_;++i)
#define DREP(i,a,b) for(int i=a,i##_end_=b;i>=i##_end_;--i)
typedef long long ll;

using namespace std;

void File(){
	freopen("bzoj2803.in","r",stdin);
	freopen("bzoj2803.out","w",stdout);
}

template<typename T>void read(T &_){
	T __=0,mul=1; char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')mul=-1;
		ch=getchar();
	}
	while(isdigit(ch))__=(__<<1)+(__<<3)+(ch^'0'),ch=getchar();
	_=__*mul;
}

const int maxn=1e6+10;
const ll base=97;
const ll ma=19260817;
const ll mb=998244353;
int T,n,s[maxn],ans;
char S[maxn];
ll pa[maxn],pb[maxn],sa[maxn],sb[maxn];

ll Hash(int l,int r,bool ty){
	if(ty==0)return ((sa[r]-sa[l-1]*pa[r-l+1])%ma+ma)%ma;
	else return ((sb[r]-sb[l-1]*pb[r-l+1])%mb+mb)%mb;
}

void init(){
	read(n);
	scanf("%s",S+1);
	n=strlen(S+1);
	REP(i,1,n)s[i]=S[i]-'a'+1;
	REP(i,1,n){
		sa[i]=(sa[i-1]*base+s[i])%ma;
		sb[i]=(sb[i-1]*base+s[i])%mb;
	}
}

bool judge(int l1,int r1,int l2,int r2){
	return Hash(l1,r1,0)==Hash(l2,r2,0) && Hash(l1,r1,1)==Hash(l2,r2,1);
}

void work(){
	int p=n/2-1;
	DREP(i,n/2,1){
		++p;
		p=min(p,n/2);
		while(!judge(i,p,n-p+1,n-i+1))--p;
		if(judge(1,i-1,n-i+2,n))ans=max(ans,p);
	}
	printf("%d\n",ans);
}

int main(){
	File();
	pa[0]=pb[0]=1;
	REP(i,1,maxn-10){
		pa[i]=pa[i-1]*base%ma;
		pb[i]=pb[i-1]*base%mb;
	}
	init();
	work();
	return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值