回文树/回文自动机(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.反编译设备树 在设备树学习的时候,如果可以看到最终生成的设备树的内容,对于我们学习设备树以及分析问题有很大帮助.这里我们 ...
随机推荐
- 使用 gitstats 来统计代码
使用 gitstats 来统计代码 github地址如下 gitstats clone地址 git clone https://github.com/hoxu/gitstats && ...
- java 中类初始化,构造方法,静态成员变量,静态块的加载顺序
1.编译和运行概念要搞清:编译即javac的过程,负责将.java文件compile成.class文件,主要是类型.格式检查与编译成字节码文件,而加载是指java *的过程,将.class文件加载到内 ...
- 拼图验证码 js,vue
可查看github网站
- Java动态代理-实战
Java动态代理-实战 只要是写Java的,动态代理就一个必须掌握的知识点,当然刚开始接触的时候,理解的肯定比较浅,渐渐的会深入一些,这篇文章通过实战例子帮助大家深入理解动态代理. 说动态代理之前,要 ...
- ubuntu下Java通过JNI调用C
下面看一个实例,如下: public class TestJNI { static { System.loadLibrary("diaoyong"); // 程序在加载时,自动加载 ...
- 访问者模式(Visitor Patten)
参考文章: http://www.importnew.com/15561.html 定义: 封装某些作用于某种数据结构中各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作. um ...
- Apache Kylin - 大数据下的OLAP解决方案
OLAPCube是一种典型的多维数据分析技术,Cube本身可以认为是不同维度数据组成的dataset,一个OLAP Cube 可以拥有多个维度(Dimension),以及多个事实(Factor Mea ...
- 【Gamma】Scrum Meeting 3
目录 写在前面 进度情况 任务进度表 Gamma阶段燃尽图 照片 写在前面 例会时间:5.27 22:30-23:30 例会地点:微信群语音通话 代码进度记录github在这里 临近期末,团队成员课程 ...
- JavaScript初探系列(八)——DOM
DOM(文档对象模型)是针对HTML和XML文档的一个API,描绘了一个层次化的节点树,允许开发人员添加.删除和修改页面的某一部分. HTML DOM 树形结构如下: 一.Node方面 (一).节点类 ...
- 【C++】C++中的类模板
基础的类模板 模板类的继承 内部声明定义普通模板函数和友元模板函数 内部声明友元模板函数+外部定义友元模板函数 声明和定义分别在不同的文件(模板函数.模板友元) C++中有一个重要特性,那就是模板类型 ...