「APIO2014」回文串 (二分+hash)

Posted FFakker

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了「APIO2014」回文串 (二分+hash)相关的知识,希望对你有一定的参考价值。

题目传送门

\\(O(|S|^2)\\) 47分做法

枚举回文串的对称轴向外扩展,暴力更新每个回文串的出现次数

用 hash+map 存储该回文串的出现次数

Lemma

结论:不同的回文串个数 \\(<= |S|\\)

证明:考虑往字符串\\(S\\)里一个个添加字符

找到以该字符为右端点的最长回文串 设对称轴为\\(X\\)

设存在其它的以该字符为右端点的回文串 \\(s\\)

必然在该最长回文串中存在一个 \\(s\'\\) 满足与 \\(s\\) 关于\\(X\\)对称

又因为 \\(s\'\\) 为回文串 因此 \\(s=s\'\\)

故只有最长回文串可能产生贡献 即每次最多只能产生1个不同的回文串

正解

发现有些大回文串中包括了很多小回文串

每次更新大回文串时都需要把被包含的小回文串暴力更新一遍

考虑如何优化对小回文串的更新过程

借鉴自动机建fail树的想法 容易想到将大回文串与小回文串连有向边 更新大回文串的时候通过有向边更新小回文串 题目即变为一个DAG上的dp问题

如果将大回文串与所有被包含的小回文串都连上边 则会出现重复计算

因此只对对称轴相同的小回文串连边

每个大回文串只用跟长度-2的小回文串连边

枚举对称轴,二分+hash 找出最长回文串长度 再从大往小连边 如果已经连过则直接break

最后跑拓扑更新答案

不同的回文串个数\\(<=|S|\\) 而每个回文串最多只会对一个回文串连边 故最多只有\\(|S|\\)条边

时间复杂度 \\(O(|S|log|S|)\\)

要用双hash 否则有很大可能被卡 反正我被卡了

如果用 Manacher 代替二分,hash表代替map,可以做到 \\(O(|S|)\\)(大概)

#include<bits/stdc++.h>
#define M 300005
const int N=1e7+7;
typedef long long ll;
using namespace std;
bool f2;
char IO;
int rd(){
	int num=0;bool f=0;
	while(IO=getchar(),IO<48||IO>57)if(IO==\'-\')f=1;
	do num=(num<<1)+(num<<3)+(IO^48);
	while(IO=getchar(),IO>=48&&IO<=57);
	return f?-num:num;
}
char S[M];
int n;
struct HASH{
	ll A[M],B[M],BS[M];
	int Bas,P;
	void build(){
		BS[0]=1;
		for(int i=1;i<=n;++i){
			A[i]=(A[i-1]*Bas+S[i])%P;
			BS[i]=BS[i-1]*Bas%P;
		}for(int i=n;i>=1;--i)
			B[i]=(B[i+1]*Bas+S[i])%P;
	}
	ll qry1(int L,int R){
		return (B[L]-B[R+1]*BS[R-L+1]%P+P)%P;
	}
	ll qry2(int L,int R){
		return (A[R]-A[L-1]*BS[R-L+1]%P+P)%P;
	}
}H1,H2;
// 猜测结论:不同的回文串个数<=|S| 
struct HASH_TABLE{
	ll val1[M],val2[M];
	int hd[N],nxt[M],cnt;
	int idx(ll x){return x%N;}
	int find(ll a,ll b){
		for(int i=hd[idx(a)];i;i=nxt[i])
			if(val1[i]==a&&val2[i]==b)return i;
		return 0;
	}
	void insert(ll a,ll b){
		val1[++cnt]=a;
		val2[cnt]=b;
		nxt[cnt]=hd[idx(a)];
		hd[idx(a)]=cnt;
	}
}MP;
int in[M];
ll len[M],cnt[M];
int hd[M],to[M],nxt[M],cnte;
void Adde(int u,int v){
	to[++cnte]=v;
	nxt[cnte]=hd[u];
	hd[u]=cnte;
}
int Check(int x,int y){
	int L=1,R=min(x,n-y+1),mid,ans=1;
	while(L<=R){
		mid=L+R>>1;
		if(H1.qry1(x-mid+1,x)==H1.qry2(y,y+mid-1)
		&&H2.qry1(x-mid+1,x)==H2.qry2(y,y+mid-1))
			ans=mid,L=mid+1;
		else R=mid-1;
	}
	return ans;
}
void Update(int a,int b){
	int Len=Check(a,b);
	ll x,y;
	for(int i=Len,lst=-1,idx;i>=1;--i){
		x=H1.qry1(a-i+1,b+i-1),y=H2.qry1(a-i+1,b+i-1);
		idx=MP.find(x,y);
		if(!idx){
			MP.insert(x,y);idx=MP.cnt;len[idx]=2*i-(a==b);
			if(~lst)Adde(lst,idx),++in[idx];
			lst=idx;
		}else{
			if(~lst)Adde(lst,idx),++in[idx];
			break;
		}
	}
	++cnt[MP.find(H1.qry1(a-Len+1,b+Len-1),H2.qry1(a-Len+1,b+Len-1))];
}
bool f1;
int main(){
//	cout<<(&f1-&f2)/1024.0/1024.0<<endl;
 	freopen("palindrome.in","r",stdin);
 	freopen("palindrome.out","w",stdout);
	scanf("%s",S+1);
	n=strlen(S+1);
	H1.Bas=233;H2.Bas=269;
	H1.P=1e9+7;H2.P=1e9+9;
	H1.build();H2.build();
	for(int i=1;i<=n;++i){
		Update(i,i);
		if(i<n&&S[i]==S[i+1])
			Update(i,i+1);
	}
	ll ans=0;
	queue<int> Q;
	for(int i=1;i<=MP.cnt;++i)
		if(!in[i])Q.push(i);
	int u,v;
	while(!Q.empty()){
		u=Q.front();Q.pop();
		ans=max(ans,1ll*cnt[u]*len[u]);
		for(int i=hd[u];i;i=nxt[i]){
			v=to[i];
			cnt[v]+=cnt[u];
			if(--in[v]==0)Q.push(v);
		}
	}
	cout<<ans;
	return 0;
}

以上是关于「APIO2014」回文串 (二分+hash)的主要内容,如果未能解决你的问题,请参考以下文章

题解APIO2014回文串

Apio2014 回文串

BZOJ3676: [Apio2014]回文串

P3649 [APIO2014]回文串(回文自动机)

[BZOJ3676][APIO2014]回文串(Manacher+SAM)

bzoj3676: [Apio2014]回文串 回文树