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

题目传送门

Solution


\(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;
}
posted @ 2021-06-04 21:41  Alnorie  阅读(122)  评论(0)    收藏  举报