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]);        }    }}
原创粉丝点击