Codeforces #30E: Tricky and Clever Password 题解

4 篇文章 0 订阅
1 篇文章 0 订阅

首先可以发现一个结论

我们枚举一个中间回文串的中心点,会发现我们一定会选择以它为中心的最长的回文串

因为考虑左边右边的两个串,如果它们与长回文串不重叠,那么取长串显然更优;如果重叠,那么不重叠的部分仍然可以取,重叠的部分已经被我的长串取掉了,也不亏。所以取长串肯定最优

对于每个长度的后缀,如果前面有多个子串和它匹配,那么显然越靠前越优

我们先求对于每一个位置,能和后缀匹配的最长长度,记为p[i]

我们枚举位置以后二分长度,字符串哈希一发就好

然后我们维护一个数组minpos,minpos[i]表示长度为i的后缀最早在哪里出现过

我们从左往右扫描p数组,对于第i个位置,minpos[1]~minpos[p[i]]的答案都不会超过i

因为是从左往右扫描的,所以如果碰到已经填过的说明有更小的答案,就不用再往下更新了,这样每个格子最多被更新一次,复杂度是线性的

然后跑一趟manacher找出每个位置为中心的最长半径

然后枚举中心点,因为考虑到minpos[i]数组是单调递增的(因为条件越来越苛刻)所以可以二分左右串的长度,检查是否不与中间串重叠就好了

总复杂度O(nlogn)

#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <utility>
#include <cctype>
#include <algorithm>
#include <bitset>
#include <set>
#include <map>
#include <vector>
#include <queue>
#include <deque>
#include <stack>
#include <cmath>
#define LL long long
#define LB long double
#define x first
#define y second
#define Pair pair<int,int>
#define pb push_back
#define pf push_front
#define mp make_pair
#define LOWBIT(x) x & (-x)
using namespace std;

const int MOD=1e9+7;
const LL LINF=2e16;
const int INF=2e9;
const int magic=348;
const double eps=1e-10;
const double pi=3.14159265;

inline int getint()
{
    char ch;int res;bool f;
    while (!isdigit(ch=getchar()) && ch!='-') {}
    if (ch=='-') f=false,res=0; else f=true,res=ch-'0';
    while (isdigit(ch=getchar())) res=res*10+ch-'0';
    return f?res:-res;
}

char ss[300048];
char s[300048];int len;

int RL[300048],maxlen[300048];

int minpos[100048];
LL hsh1[100048],hsh2[100048];
LL hsh3[100048],hsh4[100048];
LL fp1[100048],fp2[100048];
const int p=998244353;
const int MOD1=19260817,MOD2=1e9+7;

void init_hash()
{
	int i;
	fp1[0]=fp2[0]=1;
	for (i=1;i<=len;i++) fp1[i]=(fp1[i-1]*p)%MOD1,fp2[i]=(fp2[i-1]*p)%MOD2;
	for (i=1;i<=len;i++) hsh1[i]=(hsh1[i-1]*p+s[i])%MOD1,hsh2[i]=(hsh2[i-1]*p+s[i])%MOD2;
	for (i=len;i>=1;i--) hsh3[i]=(hsh3[i+1]*p+s[i])%MOD1,hsh4[i]=(hsh4[i+1]*p+s[i])%MOD2;
}

Pair gethash1(int left,int right)
{
	pair<LL,LL> res;
	res.x=((hsh1[right]-(hsh1[left-1]*fp1[right-left+1])%MOD1)%MOD1+MOD1)%MOD1;
	res.y=((hsh2[right]-(hsh2[left-1]*fp2[right-left+1])%MOD2)%MOD2+MOD2)%MOD2;
	return res;
}

Pair gethash2(int left,int right)
{
	pair<LL,LL> res;
	res.x=((hsh3[left]-(hsh3[right+1]*fp1[right-left+1])%MOD1)%MOD1+MOD1)%MOD1;
	res.y=((hsh4[left]-(hsh4[right+1]*fp2[right-left+1])%MOD2)%MOD2+MOD2)%MOD2;
	return res;
}

int main ()
{
	int i,j;
	scanf("%s",s+1);len=strlen(s+1);
	int cur=1;
	for (i=1;i<=len;i++) ss[cur++]='#',ss[cur++]=s[i];
	ss[cur]='#';
	int llen=cur,maxright=0,mpos=0;
	for (i=1;i<=llen;i++)
	{
		if (i<maxright)
			RL[i]=min(RL[2*mpos-i],maxright-i);
		else
			RL[i]=1;
		while (i-RL[i]>=1 && i+RL[i]<=llen && ss[i-RL[i]]==ss[i+RL[i]]) RL[i]++;
		if (i+RL[i]>maxright)
		{
			maxright=i+RL[i];
			mpos=i;
		}
	}
	init_hash();
	for (i=1;i<=llen;i++)
		if (ss[i]!='#') maxlen[i/2]=RL[i]-1;
	for (i=1;i<=len;i++)
	{
		int maxl=0,l=1,r=len,mid;
		while (l<=r)
		{
			mid=(l+r)>>1;
			if (gethash1(i,i+mid-1)==gethash2(len-mid+1,len))
			{
				maxl=mid;
				l=mid+1;
			}
			else
			{
				r=mid-1;
			}
		}
		if (maxl)
		{
			for (j=maxl;j>=1 && !minpos[j];j--) minpos[j]=i;
		}
	}
	int anslen=0;
	Pair ans1=mp(-1,-1),ans2=mp(-1,-1),ans3=mp(-1,-1);
	for (i=1;i<=len;i++)
	{
		if (maxlen[i]>anslen)
		{
			anslen=maxlen[i];
			ans1=ans3=mp(-1,-1);ans2=mp(i-(maxlen[i]-1)/2,i+(maxlen[i]-1)/2);
		}
		int l=1,r=len,maxl=0,mid;
		while (l<=r)
		{
			mid=(l+r)>>1;
			if (len-mid+1>i+(maxlen[i]-1)/2 && minpos[mid] && minpos[mid]+mid-1<i-(maxlen[i]-1)/2)
			{
				maxl=mid;
				l=mid+1;
			}
			else
			{
				r=mid-1;
			}
		}
		if (maxl)
		{
			if (maxl+maxl+maxlen[i]>anslen)
			{
				anslen=maxl+maxl+maxlen[i];
				ans1=mp(minpos[maxl],minpos[maxl]+maxl-1);
				ans2=mp(i-(maxlen[i]-1)/2,i+(maxlen[i]-1)/2);
				ans3=mp(len-maxl+1,len);
			}
		}
	}
	if (ans1.x==-1)
	{
		printf("1\n");
		printf("%d %d\n",ans2.x,ans2.y-ans2.x+1);
	}
	else
	{
		printf("3\n");
		printf("%d %d\n",ans1.x,ans1.y-ans1.x+1);
		printf("%d %d\n",ans2.x,ans2.y-ans2.x+1);
		printf("%d %d\n",ans3.x,ans3.y-ans3.x+1);
	}
	return 0;
}

尝试优化这个做法

首先对于求minpos[i]的过程,如果我们把原字符串反过来接在原串前面,跑一遍KMP,那么fail数组对应的就是我需要的minpos

于是这个部分可以做到O(n)

考虑最后统计答案的部分

我们预处理数组f[i]表示如果中间的回文串的左端点在i,单考虑左边可以取到的最大长度

因为minpos是单调的,所以我们仍然可以使用打标记的方法,只给没有打标记的格子更新答案,每个格子最多被更新一次,复杂度是线性的

最后枚举中心点的时候,f[i]和右边的长度取一个min就好

总复杂度O(n)

#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <utility>
#include <cctype>
#include <algorithm>
#include <bitset>
#include <set>
#include <map>
#include <vector>
#include <queue>
#include <deque>
#include <stack>
#include <cmath>
#define LL long long
#define LB long double
#define x first
#define y second
#define Pair pair<int,int>
#define pb push_back
#define pf push_front
#define mp make_pair
#define LOWBIT(x) x & (-x)
using namespace std;

const int MOD=1e9+7;
const LL LINF=2e16;
const int INF=2e9;
const int magic=348;
const double eps=1e-10;
const double pi=3.14159265;

inline int getint()
{
    char ch;int res;bool f;
    while (!isdigit(ch=getchar()) && ch!='-') {}
    if (ch=='-') f=false,res=0; else f=true,res=ch-'0';
    while (isdigit(ch=getchar())) res=res*10+ch-'0';
    return f?res:-res;
}

int len,slen;
char s[300048],ss[300048];
int RL[300048],maxright,pos,maxlen[300048],mlen[300048],minpos[300048];
int fail[300048];
int f[300048];

void getfail()
{
	int k=-1,j=0;
	fail[0]=-1;
	while (j<=slen)
	{
		if (k==-1 || ss[j+1]==ss[k+1])
			fail[++j]=++k;
		else
			k=fail[k];
	}
}

void getRL()
{
	memset(ss,'\0',sizeof(ss));
	int i,cur=1;
	for (i=1;i<=len;i++)
	{
		ss[cur++]='#';
		ss[cur++]=s[i];
	}
	ss[cur]='#';slen=cur;
	maxright=pos=0;
	for (i=1;i<=slen;i++)
	{
		if (i<maxright)
			RL[i]=min(maxright-i,RL[2*pos-i]);
		else
			RL[i]=1;
		while (i-RL[i]>=1 && i+RL[i]<=slen && ss[i-RL[i]]==ss[i+RL[i]]) RL[i]++;
		if (i+RL[i]>maxright)
		{
			maxright=i+RL[i];
			pos=i;
		}
	}
}

int main ()
{
	//freopen ("30e.in","r",stdin);
	//freopen ("30e.out","w",stdout);
	int i,j;
	scanf("%s",s+1);len=strlen(s+1);
	for (i=1;i<=len;i++) ss[i]=s[len-i+1];
	ss[len+1]='$';
	for (i=len+2;i<=len+2+len-1;i++) ss[i]=s[i-len-1];
	slen=strlen(ss+1);
	getfail();
	for (i=len+2;i<=slen;i++) mlen[i-len-1-fail[i]+1]=max(mlen[i-len-1-fail[i]+1],fail[i]);
	for (i=1;i<=len;i++)
		for (j=mlen[i];j>=1 && !minpos[j];j--)
			minpos[j]=i;
	getRL();
	//return 0;
	for (i=1;i<=slen;i++) if (ss[i]!='#') maxlen[i/2]=RL[i]-1;
	//cout<<"*"<<endl;
	for (i=len;i>=1;i--)
		if (minpos[i])
			for (j=minpos[i]+i;j<=len && !f[j];j++)
				f[j]=i;
	//return 0;
	Pair ans1=mp(-1,-1),ans2=mp(-1,-1),ans3=mp(-1,-1);int anslen=0;
	for (i=1;i<=len;i++)
	{
		int ll=i-(maxlen[i]-1)/2,rr=i+(maxlen[i]-1)/2;
		int add=min(len-rr,f[ll]);
		if (maxlen[i]+add+add>anslen)
		{
			anslen=maxlen[i]+add+add;
			ans2=mp(i-(maxlen[i]-1)/2,maxlen[i]);
			if (add)
			{
				ans1=mp(minpos[add],add);
				ans3=mp(len-add+1,add);
			}
			else
				ans1=ans3=mp(-1,-1);
		}
	}
	if (ans1.x==-1)
	{
		printf("1\n");
		printf("%d %d\n",ans2.x,ans2.y);
	}
	else
	{
		printf("3\n");
		printf("%d %d\n",ans1.x,ans1.y);
		printf("%d %d\n",ans2.x,ans2.y);
		printf("%d %d\n",ans3.x,ans3.y);
	}
	return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值