「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)的主要内容,如果未能解决你的问题,请参考以下文章