回文树/回文自动机(PAM)学习笔记
回文树(也就是回文自动机)实际上是奇偶两棵树,每一个节点代表一个本质不同的回文子串(一棵树上的串长度全部是奇数,另一棵全部是偶数),原串中每一个本质不同的回文子串都在树上出现一次且仅一次。
一个节点的fail指针指向它的最长回文后缀(不包括自身,所有空fail均连向1)。归纳容易证明,当在原串末尾新增一个字符时,回文树上至多会新增一个节点,这也证明了一个串本质不同的回文子串个数不会超过n。
建树时采用增量构造法,当考虑新字符s[i]时,先找到以s[i-1]为结尾的节点p,并不断跳fail。若代表新增回文子串的节点已存在则直接结束,否则通过fail[p]不断跳fail找到新节点的fail。
0,1号节点均不代表串,常数大于manacher。初始化fail[0]=fail[1]=1,len[1]=-1,tot=1,last=0。
[BZOJ2160]拉拉队排练
建立后缀树后树上DP求出每种回文子串的出现次数即可。
#include<cstdio>
#include<algorithm>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
typedef long long ll;
using namespace std; const int N=,mod=;
char s[N];
ll K,sm;
int n,ans=,lst,nd=,len[N],fail[N],son[N][],sz[N];
struct P{ int l,c; }c[N];
bool operator <(const P &a,const P &b){ return a.l>b.l; } int ksm(int a,int b){
int s=;
for (; b; a=1ll*a*a%mod,b>>=)
if (b & ) s=1ll*s*a%mod;
return s;
} void ext(int c,int n,char s[]){
int p=lst;
while (s[n-len[p]-]!=s[n]) p=fail[p];
if (!son[p][c]){
int np=++nd,q=fail[p];
while (s[n-len[q]-]!=s[n]) q=fail[q];
len[np]=len[p]+; fail[np]=son[q][c]; son[p][c]=np;
}
lst=son[p][c]; sz[lst]++;
} int main(){
freopen("bzoj2160.in","r",stdin);
freopen("bzoj2160.out","w",stdout);
scanf("%d%lld%s",&n,&K,s+);
len[]=-; fail[]=fail[]=;
rep(i,,n) ext(s[i]-'a',i,s);
for (int i=nd; i; i--) sz[fail[i]]+=sz[i];
rep(i,,nd) c[i-]=(P){len[i],sz[i]};
sort(c+,c+nd);
rep(i,,nd-){
if (!(c[i].l&)) continue;
ll t=min(K,(ll)c[i].c); ans=1ll*ans*ksm(c[i].l,t)%mod; K-=t;
if (!K) break;
}
printf("%d\n",K?-:ans);
return ;
}
BZOJ2160
[BZOJ3676][APIO2014]回文串
显然建出回文树后求出每个点的出现次数与长度即可。
#include<cstdio>
#include<cstring>
#include<algorithm>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
typedef long long ll;
using namespace std; const int N=;
char s[N];
ll ans;
int n,lst,nd=,len[N],fail[N],sz[N],son[N][]; void ext(int c,int n,char s[]){
int p=lst;
while (s[n-len[p]-]!=s[n]) p=fail[p];
if (!son[p][c]){
int np=++nd,q=fail[p];
while (s[n-len[q]-]!=s[n]) q=fail[q];
len[np]=len[p]+; fail[np]=son[q][c]; son[p][c]=np;
}
sz[lst=son[p][c]]++;
} int main(){
freopen("bzoj3676.in","r",stdin);
freopen("bzoj3676.out","w",stdout);
scanf("%s",s+); n=strlen(s+);
fail[]=fail[]=; len[]=-; s[]=-;
rep(i,,n) ext(s[i]-'a',i,s);
for (int i=nd; i; i--) sz[fail[i]]+=sz[i];
rep(i,,nd) ans=max(ans,1ll*sz[i]*len[i]);
printf("%lld\n",ans);
return ;
}
BZOJ3676
[CF17E]Palisection
正难则反,所有回文串对数减去不相交对数。以某个位置结尾的回文子串个数等于它在回文树上代表的节点的深度,后缀和优化一下即可。
#include<cstdio>
#include<algorithm>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
#define For(i,x) for (int i=h[x],k; i; i=nxt[i])
typedef long long ll;
using namespace std; const int N=,mod=;
char s[N];
int n,cnt,ans,nd,lst,p1[N],p2[N],dep[N],to[N],nxt[N],val[N],h[N],fail[N],len[N]; void add(int u,int v,int w){ to[++cnt]=v; nxt[cnt]=h[u]; val[cnt]=w; h[u]=cnt; } void init(){
rep(i,,nd) h[i]=fail[i]=len[i]=dep[i]=;
cnt=lst=; nd=; len[]=-; fail[]=fail[]=;
} int son(int x,int c){ For(i,x) if (val[i]==c) return k=to[i]; return ; } void ext(int c,int n,char s[]){
int p=lst;
while (s[n-len[p]-]!=s[n]) p=fail[p];
if (!son(p,c)){
int np=++nd,q=fail[p];
while (s[n-len[q]-]!=s[n]) q=fail[q];
len[np]=len[p]+; fail[np]=son(q,c); dep[np]=dep[fail[np]]+; add(p,np,c);
}
lst=son(p,c);
} int main(){
freopen("cf17e.in","r",stdin);
freopen("cf17e.out","w",stdout);
scanf("%d%s",&n,s+); init();
rep(i,,n) ext(s[i]-'a',i,s),p1[i]=dep[lst],ans=(ans+p1[i])%mod;
ans=1ll*ans*(ans-)/%mod; reverse(s+,s+n+); init();
rep(i,,n) ext(s[i]-'a',i,s),p2[n-i+]=dep[lst];
for (int i=n; i; i--) p2[i]=(p2[i]+p2[i+])%mod;
rep(i,,n) ans=(ans-1ll*p1[i]*p2[i+]%mod+mod)%mod;
printf("%d\n",ans);
return ;
}
CF17E
[Aizu2292]Common Palindromes
给定S,T,询问有多少(l1,r1,l2,r2)使得S[l1,r1]回文且S[l1,r1]=T[l2,r2]。
显然对S建出回文自动机然后T在上面跑,记录每个S中回文串的出现次数以及T中有多少个子串与此串匹配。注意初始x=1(可以认为回文树的根是1),且匹配是不仅要看此节点是否有对应子节点,也要看s[i-len[x]-1]是否等于s[i]。
#include<cstdio>
#include<cstring>
#include<algorithm>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
typedef long long ll;
using namespace std; const int N=;
char s[N];
ll ans;
int n,lst,nd=,f[N],son[N][],fail[N],len[N],sz[N]; void ext(int c,int n,char s[]){
int p=lst;
while (s[n-len[p]-]!=s[n]) p=fail[p];
if (!son[p][c]){
int np=++nd,q=fail[p];
while (s[n-len[q]-]!=s[n]) q=fail[q];
len[np]=len[p]+; fail[np]=son[q][c]; son[p][c]=np;
}
sz[lst=son[p][c]]++;
} int main(){
freopen("Aizu2292.in","r",stdin);
freopen("Aizu2292.out","w",stdout);
len[]=-; fail[]=fail[]=;
scanf("%s",s+); n=strlen(s+);
rep(i,,n) ext(s[i]-'A',i,s);
scanf("%s",s+); n=strlen(s+); int x=;
rep(i,,n){
int c=s[i]-'A';
while (x!= && (!son[x][c] || s[i]!=s[i-len[x]-])) x=fail[x];
if (son[x][c] && s[i]==s[i-len[x]-]) x=son[x][c],f[x]++;
}
for (int i=nd; i; i--) f[fail[i]]+=f[i],sz[fail[i]]+=sz[i];
rep(i,,nd) ans+=1ll*f[i]*sz[i];
printf("%lld\n",ans);
return ;
}
Aizu2292
[BZOJ2342][SHOI2011]双倍回文
就是求后半段也为回文串的回文串个数,在fail树上DFS并维护每个长度的回文串个数即可。
或者考虑求half[i]表示节点i的最深祖先满足len[half[i]]<=len[i]/2,这个同样可以在建树的时候求得。
#include<cstdio>
#include<algorithm>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
typedef long long ll;
using namespace std; const int N=;
char s[N];
int n,ans,lst,nd=,fail[N],son[N][],len[N],half[N]; void ext(int c,int n,char s[]){
int p=lst;
while (s[n-len[p]-]!=s[n]) p=fail[p];
if (!son[p][c]){
int np=++nd,q=fail[p];
while (s[n-len[q]-]!=s[n]) q=fail[q];
len[np]=len[p]+; fail[np]=son[q][c]; son[p][c]=np;
if (len[np]==) half[np]=;
else{
int pos=half[p];
while (s[n-len[pos]-]!=s[n] || (len[pos]+)*>len[np]) pos=fail[pos];
half[np]=son[pos][c];
}
}
lst=son[p][c];
} int main(){
freopen("bzoj2342.in","r",stdin);
freopen("bzoj2342.out","w",stdout);
scanf("%d%s",&n,s+); len[]=-; fail[]=fail[]=;
rep(i,,n) ext(s[i]-'a',i,s);
rep(i,,nd) if (len[half[i]]*==len[i] && len[i]%==) ans=max(ans,len[i]);
printf("%d\n",ans);
return ;
}
BZOJ2342
回文树/回文自动机(PAM)学习笔记的更多相关文章
- [模板] 回文树/回文自动机 && BZOJ3676:[Apio2014]回文串
回文树/回文自动机 放链接: 回文树或者回文自动机,及相关例题 - F.W.Nietzsche - 博客园 状态数的线性证明 并没有看懂上面的证明,所以自己脑补了一个... 引理: 每一个回文串都是字 ...
- 回文树(回文自动机) - URAL 1960 Palindromes and Super Abilities
Palindromes and Super Abilities Problem's Link: http://acm.timus.ru/problem.aspx?space=1&num=19 ...
- 回文树(回文自动机PAM)小结
回文树学习博客:lwfcgz poursoul 边写边更新,大概会把回文树总结在一个博客里吧... 回文树的功能 假设我们有一个串S,S下标从0开始,则回文树能做到如下几点: 1.求串S前缀0~ ...
- 回文树(回文自动机) - BZOJ 3676 回文串
BZOJ 3676 回文串 Problem's Link: http://www.lydsy.com/JudgeOnline/problem.php?id=3676 Mean: 略 analyse: ...
- BZOJ 3676: [Apio2014]回文串 回文树 回文自动机
http://www.lydsy.com/JudgeOnline/problem.php?id=3676 另一种更简单更快常数更小的写法,很神奇……背板子. #include<iostream& ...
- JavaScript权威设计--JavaScript脚本化文档Document与CSS(简要学习笔记十五)
1.Document与Element和TEXT是Node的子类. Document:树形的根部节点 Element:HTML元素的节点 TEXT:文本节点 >>HtmlElement与 ...
- PAM学习笔记
想了想 还是要先把字符串的东西先都学完告一段落了再说 时间不多了 加油~. PAM 回文自动机 比SAM简单到不知道哪里去了. 回文自动机和其他自动机一样有字符集 有状态 有转移. 一个字符串的回文自 ...
- 「AC自动机」学习笔记
AC自动机(Aho-Corasick Automaton),虽然不能够帮你自动AC,但是真的还是非常神奇的一个数据结构.AC自动机用来处理多模式串匹配问题,可以看做是KMP(单模式串匹配问题)的升级版 ...
- 设备树(device tree)学习笔记
作者信息 作者:彭东林 邮箱:pengdonglin137@163.com 1.反编译设备树 在设备树学习的时候,如果可以看到最终生成的设备树的内容,对于我们学习设备树以及分析问题有很大帮助.这里我们 ...
随机推荐
- 轮播模仿臭美APP,vue,swiper
介绍:轮播使用了swiper,重要用于移动端滑动,详情可查看官网 1.首先用npm安装 npm install swiper 2.main.js 中引入CSS import 's ...
- 【POJ1426】Find The Multiple
本题传送门 本题知识点:深度优先搜索 | 宽度优先搜索 题意很简单,让我们找一个只有1和0组成的十位数是n的倍数的数. 这题一开始吓到我了--因为Output里说输出的长度最长不超过100位???那是 ...
- MySQL数据库中文乱码问题
mysql> select * from books; +-----+---------------------------------+---------+-------------+---- ...
- 浅谈UDF并行
首先我们来看说明UDF并行流程的这个图 网格和求解数据分布和储存在计算节点(compute-node)处理器上,而对于GUI界面和主机(host)节点上不存储任何数据,主机节点将命令从GUI传递到0节 ...
- 区间dp专题练习
区间dp专题练习 题意 1.Equal Sum Partitions ? 这嘛东西,\(n^2\)自己写去 \[\ \] \[\ \] 2.You Are the One 感觉自己智力被吊打 \(dp ...
- oracle 如何快速删除两表非关联数据(脏数据)?
1.情景展示 现在有两者表,表1中的主键id字段和表2的index_id相对应.如何删除两表非关联数据? 2.解决方案 --第1步 delete from VIRTUAL_CARD t where ...
- Google Dremel架构
Dremel 是Google 的“交互式”数据分析系统.Google开发了Dremel将处理时间缩短到秒级,作为MapReduce的有力补充.Apache推出Dremel的开源实现Drill,将Dre ...
- 日常开发中的shell小技巧
工具推荐 命令行中很方便的代码统计工具---cloc 强大的分屏工具---tmux 最舒服的markdown书写工具---typora markdown图床推荐--七牛云 模拟生成熵(避免暴力手搓键盘 ...
- JVM探究之 —— 类文件结构(脑图)
- Vue使用NProgress
NProgress是页面跳转是出现在浏览器顶部的进度条官网:http://ricostacruz.com/nprogress/github:https://github.com/rstacruz/np ...