木xx木大
2020-12-18 22:34:24
题外话:字符串算法真的好难理解!(当然也是因为我太菜了)这篇博客的内容我用了一整天才完成,希望能写得还算清楚吧。
回文自动机(PAM)是一种处理回文串的工具。它的每个结点表示一个本质不同的回文串。
因为回文串长度分为奇数和偶数,为了方便处理,回文自动机由两棵树组成, 一棵树中的节点对应的回文子串长度均为奇数,另一棵树中的节点对应的回文子串长度均为偶数。
一个节点的
我们还需要在每个节点上维护此节点对应回文子串的长度
我们称两棵树的根为奇根、偶根,分别代表长度为
考虑构造完前
设第一个满足如上条件的点是
P5496 【模板】回文自动机(PAM)
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
namespace FGF
{
int n,m;
const int N=5e5+5;
char s[N];
int fai[N],cnt[N],len[N],nxt[N][30],tot,lst,pos;
int getfail(int x)
{
while(s[pos-len[x]-1]!=s[pos])x=fai[x];
return x;
}
void inser(char c)
{
int x=c-'a',p=getfail(lst);
if(!nxt[p][x])
{
len[++tot]=len[p]+2;
fai[tot]=nxt[getfail(fai[p])][x];
nxt[p][x]=tot;
cnt[tot]=cnt[fai[tot]]+1;
}
lst=nxt[p][x];
}
void work()
{
scanf("%s",s+1);n=strlen(s+1);
s[0]='#',fai[0]=1;
len[0]=0,len[1]=-1,tot=1;
for(pos=1;pos<=n;pos++)
{
inser(s[pos]);
s[pos+1]=(s[pos+1]-97+cnt[lst])%26+97;
printf("%d ",cnt[lst]);
}
}
}
int main()
{
FGF::work();
return 0;
}
定义:若
下面陈列一些也许有用的性质,证明详见 oi-wiki
若 +
表示直接拼接)的字符串,则有下面三条性质
如果
如果
因为插入的时候,我们只在当前点结尾的最长回文串的结点
例题: P3649 [APIO2014]回文串
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
namespace FGF
{
int n,m;
const int N=3e5+5;
char s[N];
int fai[N],nxt[N][30],len[N],cnt[N],lst,tot;
ll ans;
int getfail(int x,int y)
{
while(s[y-1-len[x]]!=s[y])x=fai[x];
return x;
}
void build()
{
s[0]='#';fai[0]=1,lst=0;
len[0]=0,len[tot=1]=-1;
for(int i=1;s[i];i++)
{
int x=s[i]-'a',p=getfail(lst,i);
if(!nxt[p][x])
{
len[++tot]=len[p]+2;
fai[tot]=nxt[getfail(fai[p],i)][x];
nxt[p][x]=tot;
}
lst=nxt[p][x];cnt[lst]++;
}
}
void work()
{
scanf("%s",s+1);
n=strlen(s+1);
build();
for(int i=tot;i;i--)
cnt[fai[i]]+=cnt[i],ans=max(ans,1LL*cnt[i]*len[i]);
printf("%lld",ans);
}
}
int main()
{
FGF::work();
return 0;
}
引入一个
当我们新建一个节点后,如果它的长度小于等于 2,那么这个节点的
否则,从它父亲的
不同的人对这个指针的叫法不同,但本质是相同的。
例题:P4287 [SHOI2011]双倍回文
一个字符串满足双倍回文,当且仅当它的长度是 4 的倍数且它的
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
namespace FGF
{
int n,m,ans;
const int N=5e5+5;
char s[N];
int fai[N],lst,len[N],f[N],nxt[N][30],tot;
int getfail(int x,int y)
{
while(s[y-len[x]-1]!=s[y])x=fai[x];
return x;
}
void build()
{
s[0]='#';len[tot=1]=-1;fai[0]=1;
for(int i=1;i<=n;i++)
{
int x=s[i]-'a',p=getfail(lst,i);
if(!nxt[p][x])
{
len[++tot]=len[p]+2;
fai[tot]=nxt[getfail(fai[p],i)][x];
nxt[p][x]=tot;
if(len[tot]<=2)f[tot]=fai[tot];
else
{
int tmp=f[p];
while(s[i-len[tmp]-1]!=s[i]||len[tmp]+2>len[tot]>>1)tmp=fai[tmp];
f[tot]=nxt[tmp][x];
}
}
lst=nxt[p][x];
}
}
void work()
{
scanf("%d",&n);
scanf("%s",s+1);
build();
for(int i=1;i<=tot;i++)
if(len[i]%4==0&&len[i]==len[f[i]]*2)ans=max(ans,len[i]);
printf("%d",ans);
}
}
int main()
{
FGF::work();
return 0;
}
P4762 [CERC2014]Virus synthesis
先建回文自动机,然后记
显然 2 操作越多越好,而经过 2 操作之后的串必定是一个回文串,所以最后的答案肯定是由一个回文串+不断暴力添加得来,所以有
考虑回文数上父亲到儿子的转移
设回文串
考虑
可以先填好一部分,再用 2 操作。发现可以这样操作的串的长度必须小于等于当前串长度的一半,这一性质刚好符合
代码细节:注意赋初值和清零!
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
namespace FGF
{
int n,m;
const int N=1e5+5;
char s[N];
int val[N],f[N],fai[N],len[N],nxt[N][5],ans,dp[N],lst,tot;
int getfail(int x,int y)
{
while(s[y-len[x]-1]!=s[y])x=fai[x];
return x;
}
void build()
{
s[0]='#';len[1]=-1,tot=1,lst=0,fai[0]=1,len[0]=0;
for(int i=1;i<=n;i++)
{
int x=val[(int)s[i]],p=getfail(lst,i);
if(!nxt[p][x])
{
len[++tot]=len[p]+2;
memset(nxt[tot],0,sizeof(nxt[tot]));
fai[tot]=nxt[getfail(fai[p],i)][x];
nxt[p][x]=tot;
if(len[tot]<=2)f[tot]=fai[tot];
else
{
int tmp=f[p];
while(s[i-len[tmp]-1]!=s[i]||len[tmp]+2>len[tot]/2)tmp=fai[tmp];
f[tot]=nxt[tmp][x];
}
}
lst=nxt[p][x];
}
}
queue<int> q;
void work()
{
scanf("%d",&m);
val['A']=0,val['T']=1,val['C']=2,val['G']=3;
while(m--)
{
memset(nxt[0],0,sizeof(nxt[0])),memset(nxt[1],0,sizeof(nxt[1]));
scanf("%s",s+1);
ans=n=strlen(s+1);
build();
for(int i=2;i<=tot;i++)dp[i]=len[i];
dp[0]=1;q.push(0);
while(q.size())
{
int u=q.front();q.pop();
for(int i=0,x=nxt[u][i];i<4;i++,x=nxt[u][i])
if(x)
{
dp[x]=min(dp[x],min(dp[u]+1,dp[f[x]]+1+len[x]/2-len[f[x]]));
ans=min(ans,dp[x]+n-len[x]);
q.push(x);
}
}
printf("%d\n",ans);
}
}
}
int main()
{
//freopen("1.in","r",stdin);
//freopen("1.out","w",stdout);
FGF::work();
return 0;
}
划重点 :这部分是本篇博客最难理解也最巧妙的地方!
问题描述:
给定一个长度为
n 的字符串,将其中若干个位置断开,要求每个分割出来的串都满足回文,询问最少需要断开几次/断开的方案数。n\le 10^5
设
回文树上的每个节点
考虑将一个等差数列表示的所有回文串的
假设当前枚举到第
考虑优化后的复杂度。回顾一下上面提到过的性质:
根据这个结论,如果按
感觉我讲的不是很清楚。如果没看懂建议参考 oi-wiki 和这位巨佬的博客。(讲得太棒啦!证明也很清楚orz)
例题:CF932G Palindrome Partition
令
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
namespace FGF
{
int n,m;
const int N=1e6+5;
const int mo=1e9+7;
char s[N],a[N];
int dif[N],sl[N],nxt[N][30],fai[N],len[N],tot,lst;
ll g[N],dp[N];
int getfail(int x,int y)
{
while(a[y-len[x]-1]!=a[y])x=fai[x];
return x;
}
void inser(char c,int y)
{
int x=c-'a',p=getfail(lst,y);
if(!nxt[p][x])
{
len[++tot]=len[p]+2;
fai[tot]=nxt[getfail(fai[p],y)][x];
nxt[p][x]=tot;
dif[tot]=len[tot]-len[fai[tot]];
if(dif[tot]==dif[fai[tot]])sl[tot]=sl[fai[tot]];
else sl[tot]=fai[tot];
}
lst=nxt[p][x];
}
void work()
{
scanf("%s",s+1);
n=strlen(s+1);
if(n&1)
{
puts("0");
return;
}
for(int i=1;i<=n;i+=2)a[i]=s[(i+1)/2];
reverse(s+1,s+n+1);
for(int i=2;i<=n;i+=2)a[i]=s[(i+1)/2];
dp[0]=1;
s[0]='#',len[1]=-1,fai[0]=1,tot=1;
for(int i=1;i<=n;i++)
{
inser(a[i],i);
for(int x=lst;x;x=sl[x])
{
g[x]=dp[i-dif[x]-len[sl[x]]];
if(dif[x]==dif[fai[x]])g[x]=(g[x]+g[fai[x]])%mo;
if(i%2==0)dp[i]=(dp[i]+g[x])%mo;
}
}
printf("%lld",dp[n]);
}
}
int main()
{
FGF::work();
return 0;
}
再丢个题:CF906E Reverses。好像也是要这样优化来做的,以后有时间再补吧
upd:
补了。令
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
namespace FGF
{
int n,m;
const int N=1e6+5;
char s[N],a[N],b[N];
int dif[N],sl[N],tot,len[N],dp[N],fai[N],lst,g[N],pre[N],ans[N],nxt[N][26];
int getfail(int x,int y)
{
while(s[y-len[x]-1]!=s[y])x=fai[x];
return x;
}
void inser(int x,int y)
{
int pos=getfail(lst,y);
if(!nxt[pos][x])
{
len[++tot]=len[pos]+2;
fai[tot]=nxt[getfail(fai[pos],y)][x];
nxt[pos][x]=tot;
dif[tot]=len[tot]-len[fai[tot]];
if(dif[tot]==dif[fai[tot]])sl[tot]=sl[fai[tot]];
else sl[tot]=fai[tot];
}
lst=nxt[pos][x];
}
void work()
{
scanf("%s%s",a+1,b+1);
n=strlen(a+1);
for(int i=1,cnt=0;i<=n;i++)
s[++cnt]=a[i],s[++cnt]=b[i];
n*=2;
memset(dp,0x3f,sizeof(dp)),memset(g,0x3f,sizeof(g));
s[0]='#',tot=1,len[1]=-1,dp[0]=0,g[0]=0,fai[0]=1;
for(int i=1;i<=n;i++)
{
inser(s[i]-'a',i);
for(int x=lst;x;x=sl[x])
{
g[x]=dp[i-dif[x]-len[sl[x]]],pre[x]=i-dif[x]-len[sl[x]];
if(dif[x]==dif[fai[x]]&&g[fai[x]]<g[x])g[x]=g[fai[x]],pre[x]=pre[fai[x]];
if(i%2==0&&g[x]+1<dp[i])dp[i]=g[x]+1,ans[i]=pre[x];
if(i%2==0&&s[i]==s[i-1]&&dp[i-2]<dp[i])dp[i]=dp[i-2],ans[i]=i-2;
}
}
if(dp[n]>=0x3f3f3f3f)
{
puts("-1");
return;
}
printf("%d\n",dp[n]);
for(int x=n;x;x=ans[x])
if(x-ans[x]!=2)printf("%d %d\n",ans[x]/2+1,x/2);
}
}
int main()
{
FGF::work();
return 0;
}
参考资料