[JZOJ3402] GDOI2014模拟Pty的字符串

Posted jz-597

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[JZOJ3402] GDOI2014模拟Pty的字符串相关的知识,希望对你有一定的参考价值。

题目

给你一棵每条边从父亲指向儿子的树,每条边上面有一个字母。
从树上的任意一点出发,走出的路径就是对应一个子串。
(这不是\(Trie\),因为每个父亲可能会连出字母相同的边)
再给你一个字符串\(S\),让你求\(S\)的子串和树上路径的对应个数。


思考历程

一开始以为路径是从根节点出发,于是我就想,这难道不是一个AC自动机的裸题吗?
啪啪啪地就把AC自动机打了上去……
然后发现样例过不去……
于是终于理解完题目大意,开始死磕。
但是一直都没有放弃AC自动机的做法。
一波乱搞后,我终于爆0了。


正解

后来才意识到其实这题是后缀自动机。
于是就开始自己刚后缀自动机的做法了……
大体思路是处理出树上的每个子串的出现次数,然后跟\(S\)的子串进行匹配。
先想想前面的这个问题。
首先,由于这是一棵树,所以就对整棵树建广义后缀自动机
至于这是什么东西……我想就不该在这里赘述了。
然后有个点\(i\)从树的根部往下跑。跑的时候\(root\)\(i\)路径所组成的字符串的所有后缀统计进来
\(i\)在跑的时候也有个点\(t\)在后缀自动机上跑。对于每对\((i,t)\),就将\(t\)\(fail\)链上的所有统计次数\(num+1\)
显然,由于节点所表示的子串的\(right\)集合相同,所以\(right\)集合大小相同,所以它们的出现次数也相同。所以\(num\)表示的是这个节点的所有子串各自出现的次数。
在实现的时候,可以先在\(t\)点打个标记,跑完之后\(fail\)树上标记上传就好了(如果你喜欢就打树链剖分吧~~(手动滑稽))。
这样我们就处理出树上的每个子串的出现次数了,接下来是匹配的问题。
也是枚举个\(i\)表示子串的右边界,也有个\(t\)在后缀自动机上面跳。
\(l\)为以\(i\)为结尾的在树中出现过的最长的子串长度。
显然,\(t\)在跳的过程中保证了\(t_minlen\leq l\leq t_maxlen\)
由于子串\(S_i-l+1..i\)的所有后缀都要记录进答案中,而\(t\)\(fail\)链上所有节点表示的子串都是它的后缀,所以就将\(fail\)链上的出现次数全部加上(即为累加\(num*(maxlen-minlen+1)\))。具体实现的时候可以用个前缀和将祖先的出现次数全部存下来,记作\(sum\)
我们计算的是长度在\([1,l]\)的后缀,可以拆成\([1,t_minlen-1]\)\([t_minlen,l]\)这两段,前者的答案是\(t_fail_sum\),后者的答案为\(t_num(l-t_fail_maxlen)\),加起来就行了。
然后问题就变成了求\(l\)的具体值。
这实际上很简单,只不过我之前想了很久,甚至恨不得再打一个AC自动机来求。
在一开始的时候\(l=0\)。后来,当\(t\)可以往前走的时候,\(l\)的值也相应地\(+1\)。当\(t\)要跳\(fail\)的时候,就将\(l\)\(t_fail_maxlen\)取个最小值。具体原因不再赘述。
于是这道题就没了……


代码

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 800010
#define LEN 8000010
int n;
struct EDGE
    int to,c;
    EDGE *las;
 e[N];
int ne;
EDGE *last[N];
struct Node
    Node *c[3],*fail;
    int len;
    int num;
    long long sum;
 d[N*2];
int cnt;
Node *S,*T;
Node *mp[N];
inline void insert(int ch)
    if (T->c[ch] && T->c[ch]->len==T->len+1)
        T=T->c[ch];
        return;
    
    Node *nw=&d[++cnt],*p,*q;
    nw->len=T->len+1;
    for (p=T;p && !p->c[ch];p=p->fail)
        p->c[ch]=nw;
    if (!p)
        nw->fail=S;
    else
        q=p->c[ch];
        if (p->len+1==q->len)
            nw->fail=q;
        else
            Node *clone=&d[++cnt];
            memcpy(clone,q,sizeof(Node));
            clone->len=p->len+1;
            for (;p && p->c[ch]==q;p=p->fail)
                p->c[ch]=clone;
            nw->fail=q->fail=clone;
        
    
    T=nw;

inline void build()
    static int q[N];
    T=S=&d[++cnt];
    int head=0,tail=1;
    q[1]=1;
    mp[1]=S;
    do
        int x=q[++head];
        for (EDGE *ei=last[x];ei;ei=ei->las)
            T=mp[x];
            insert(ei->c);
            mp[ei->to]=T;
            q[++tail]=ei->to;
        
    
    while (head!=tail);

inline void hang()
    static pair<int,Node*> q[N];
    int head=0,tail=1;
    q[1]=1,S;
    do
        ++head;
        int x=q[head].first;
        Node *ht=q[head].second;
        for (EDGE *ei=last[x];ei;ei=ei->las)
            Node *tt=ht;
            while (tt!=S && !tt->c[ei->c])
                tt=tt->fail;
            if (tt->c[ei->c])
                tt=tt->c[ei->c];
            tt->num++;
            q[++tail]=ei->to,tt;
        
    
    while (head!=tail);

Node *q[N];
bool vis[N];
inline void get_sum()
    int head=0,tail=1;
    q[1]=S;
    vis[1]=1;
    do
        Node *x=q[++head];
        for (int i=0;i<3;++i)
            if (x->c[i] && !vis[x->c[i]-d])
                vis[x->c[i]-d]=1;
                q[++tail]=x->c[i];
            
    
    while (head!=tail);
    for (int i=tail;i>=2;--i)
        Node *x=q[i];
        x->fail->num+=x->num;
    
    for (int i=2;i<=tail;++i)
        Node *x=q[i];
        x->sum=x->fail->sum+1ll*x->num*(x->len-x->fail->len);
    

char s[LEN];
int main()
    scanf("%d",&n);
    for (int i=2;i<=n;++i)
        int fa;
        char c[2];
        scanf("%d%s",&fa,c);
        e[ne]=i,*c-'a',last[fa];
        last[fa]=e+ne++;
    
    build(),hang(),get_sum();
    Node *t=S;
    scanf("%s",s);
    long long ans=0;
    int len=0;
    for (char *ch=s;*ch;++ch)
        while (t!=S && !t->c[*ch-'a'])
            t=t->fail;
            len=min(len,t->len);
        
        if (t->c[*ch-'a'])
            t=t->c[*ch-'a'];
            len++;
        
        if (t!=S)
            ans+=t->fail->sum+1ll*t->num*(len-t->fail->len);
    
    printf("%lld\n",ans);
    return 0;

总结

这也算是一道后缀自动机的模板题吧……
看来还是不够熟练啊……

以上是关于[JZOJ3402] GDOI2014模拟Pty的字符串的主要内容,如果未能解决你的问题,请参考以下文章

jzoj 4475. GDOI2016模拟4.25征途

[jzoj5073 GDOI2017第二轮模拟] 影魔

JZOJ 5048GDOI2017模拟一试4.11IQ测试

JZOJ4419GDOI2016模拟4.2hole(四~三维偏序问题)

[jzoj 6084] [GDOI2019模拟2019.3.25] 礼物 [luogu 4916] 魔力环 解题报告(莫比乌斯反演+生成函数)

gdoi2018&jzoj5698 密码锁