HDU 4622 后缀自动机
来源:互联网 发布:盈透证券骗局 知乎 编辑:程序博客网 时间:2024/06/15 09:35
题意
一个字符串,问一段区间内的不重复子串有多少个。
关于后缀自动机
这道题基本上算是后缀自动机的模板题了。但是后缀自动机看了好久,刘汝佳在那本紫书中提到了三个数据结构的难点,现在看来后缀自动机算是第二难的。(毕竟可持久化平衡树有rope)
后缀自动机的话,网上有很多教程,但是即便教程很多,后缀自动机也依然很难。这里强烈推荐fanhq后缀自动机教程
这篇教程将后缀自动机的构建转化成了后缀树的构建,这样的话就容易理解很多了(毕竟自动机是个高端货,而树就平易近人很多)
我们可以从多个角度来理解后缀自动机,首先的话,后缀自动机的是一个自动机,这个自动机,从ROOT点开始,走到所有点,所组成的字符串集合恰好是所有子串的集合。
其次的话,我们还可以将后缀自动机看成一棵后缀树,当然,这棵后缀树不同人也可以看出来不同的东西,主流的观点认为这棵后缀树是逆序串的后缀树,不过我们也可以认为这是一棵叶子节点为前缀集合的树。
具体如下图所示(根据字符串abcbc构建的后缀自动机):
其中实线代表的是后缀树的边,虚线代表的是后缀自动机的边。很明显便能发现是符合上面的论述的。
题解
终于到了题解部分,关于这道题,我们还是要从那张图上找规律。我们可以发现后缀自动机(后缀树)有一个这样的规律,图片中的数字标记的是到这个点所组成的最长的字符串长度,例如c这个点可以组成bc和c两个字符串,我们便取最长的2作为这个点的值。然后我们便可以发现len[q]-len[par[q]]便是一个点新增的字符子串个数。
比如说(ab)c这个点,由于这个点是从c这个点转移而来的,那么我们就要讨论后缀为C的字符子串。我们可以发现能形成3个子串,abc,bc,c,由于bc和c是属于c这个点的,所以实际上增加的字符子串只有一个。然后我们可以再去验证一个点,比如说我们可以验证(abcb)c这个点,我们可以得到五个子串,abcbc,bcbc,cbc,bc,c,我们可以发现bc和c是属于c这个点的,然后剩余的三个字串是这个点所独有的子串,因此新增的子串数量就是5-2==3。
很有趣的性质,我们可以简单分析一下为什么会出现这种有趣的性质。一切还要从构建这个后缀自动机说起。如果我们目前拥有了一个点,比如说bc,这时候要新增一个abc的点,如何保证子串bc和c不会重复计算呢?如果我们仔细读一下后缀自动机的代码就可以意识到,如果len(abc)==3且len(bc)==2,那么abc这个串一定会接到bc这个串的对应的点后面。这样的话,根据我们上面讨论的那个公式,只有abc这个串会被计算,所以不会产生重复的子串。
当然,有可能不会这么巧。比如说abcbc这个点,我们可以发现abcbc这个点要构建在abc这个点的后面(这里讨论的是后缀自动机构建时的过程,不是最终的结果)。如果直接添加在后面,那么就会存在子串数量统计少了的问题,因为abcbc并不是以abc为后缀的。这时候我们就需要提取后缀,我们发现公共后缀是bc,这样的话,我们可以将bc提取出来作为一个父节点,然后我们就成功解决了这个问题。
我们可以发现,这个规律和后缀自动机的构建过程是相互印证的。通过仔细分析这个规律,可以更好的理解为什么后缀自动机需要这样构造。
代码
#include <iostream>#include<cstdio>#include<algorithm>#include<cstring>#include<vector>#include<cmath>#include<queue>#include<string>#include<set>#include<map>#include<bitset>#include<stack>#include<string>#define UP(i,l,h) for(int i=l;i<h;i++)#define DOWN(i,h,l) for(int i=h-1;i>=l;i--)#define W(a) while(a)#define MEM(a,b) memset(a,b,sizeof(a))#define LL long long#define INF 0x3f3f3f3f#define MAXN 4010#define MOD 1000000007#define EPS 1e-3using namespace std;char s[MAXN];int sum[MAXN][MAXN];struct Suffix_Auto { int allc,last,par[MAXN<<1],len[MAXN<<1],trans[MAXN<<1][26]; int newNode() { int now=++allc; MEM(trans[now],0); return now; } void init() { allc=0; last=newNode(); par[last]=len[last]=0; } int extend(int c) { int p=last,np=newNode(); len[np]=len[last]+1; for(; p&&!trans[p][c]; p=par[p]) trans[p][c]=np; if(!p) par[np]=1; else { int q=trans[p][c]; if(len[q]==len[p]+1) par[np]=q; else { int nq=++allc; par[nq]=par[q]; len[nq]=len[p]+1; memcpy(trans[nq],trans[q],sizeof(trans[q])); par[np]=par[q]=nq; for(trans[p][c]=nq,p=par[p]; p&&trans[p][c]==q; p=par[p]) trans[p][c]=nq; } } last=np; return len[np]-len[par[np]]; }};Suffix_Auto sa;int main() { int t; scanf("%d",&t); W(t--) { MEM(sum,0); scanf("%s",s); int q; int len=strlen(s); UP(i,0,len) { sa.init(); UP(j,i,len) { sum[i+1][j+1]=sum[i+1][j]+sa.extend(s[j]-'a'); } } scanf("%d",&q); W(q--) { int a,b; scanf("%d%d",&a,&b); printf("%d\n",sum[a][b]); } }}
- hdu 4622(后缀自动机)
- HDU 4622 后缀自动机
- HDU 4622 后缀自动机
- hdu 4622 Reincarnation (后缀自动机)
- hdu 4622 Reincarnation (后缀自动机)
- HDU 4622(后缀自动机)
- HDU 4622 Reincarnation 后缀自动机
- hdu 4622(后缀自动机|后缀数组)
- Hdu 4622 Reincarnation 后缀数组/后缀自动机
- HDU 4622 Reincarnation(后缀自动机)
- hdu 4622 Reincarnation(后缀数组|后缀自动机|KMP)
- HDU 4622 Reincarnation 后缀数组 或 后缀自动机
- hdu 4622 后缀数组计数问题||后缀自动机
- hdu 4436 str2int (后缀自动机)
- HDU 4270 SAM 后缀自动机
- HDU 4436 str2int【后缀自动机】
- HDU 4436(后缀自动机)
- 后缀自动机1003 HDU 4416
- FTP主动模式和被动模式
- PHP面向对象数据库的连接
- Altium Designed绘制PCB时出现Access violation at address 22D4F8DA in module 'ADVPCB.DLL'. Read of address错
- P1634 禽兽的传染病
- C# 委托(delegate)的使用
- HDU 4622 后缀自动机
- 项目调研整理
- 常见数据类型字节数
- CUDA安装笔记(喜大普奔,终于成功了)
- 「GPUImage」IOS初学者容易掉入的坑
- Android -去除标题/no title bar
- java初级重点
- Validate Binary Search Tree
- AngularJS Error: [$compile:tplrt]报错