YYR字符串 魔法串 [Trie图][Fail树][主席树][补全AC自动机]

魔法串(magic.c/cpp/pas)

3.1 题目描述
给你一棵n+1个结点的有根树,结点从0到n标号,其中0为根结点。
这是一棵魔法树。这棵树的每条边有一个魔力值,同一个结点连向不同子结点的边的魔力值不同。一个结点所代表的魔法串是从根一直走到这个结点,经过的魔力值依次排列形成的有序序列,另外,一个串是魔法串当且仅当它被一个结点所代表。
现在,为了使用强大的魔法,你需要对每个魔法串,找到最长的是它后缀的魔法串。为了方便输出,你只需要输出代表这个魔法串的结点的标号即可。若没有这个魔法串,请输出0。
3.2 输入格式
第一行一个整数n,代表除根以外的结点个数。
第二行 n个整数,第i个整数P_i代表标号为i的结点的父亲标号。
第三行 n个整数,第i个整数C_i代表标号为i的结点连向父亲的边的魔力值。
3.3 输出格式
输出一行n个整数,第i个整数表示第i个结点代表的魔法串的答案。
3.4 样例输入
7
0 0 1 1 2 4 5
1 2 3 2 1 1 3
3.5 样例输出
0 0 02 1 5 3
3.6 数据范围与约定
对于30%的数据,保证1<=n<=2000。
对于100%的数据,保证1<=n<=200000,0<=P_i

题解

好题!对于fail树,因为没有很好的理解,这道题就GG了,其实代码中的看起来很显眼的主席树不是重点,重点是Fail数组的更新方式。

考虑补全AC自动机(Trie图),考虑一个结点u所连出的转移边与fail[u]所连出的转移边的关系,只有u直接连出的边会影响这些转移边,而边数是n-1条。于是我们考虑将fail[u]的转移边全部复制给u,再在此基础上对u的转移边进行修改。这个如何实现?
用可持久化线段树维护每个结点的转移边即可。

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
char s[1000005];
int n,cnt=1,pos[222],a[1000005][26],sum[1000005],p[1000005],q[1000005];
inline void insert(int &pos){
    scanf("%s",s);
    int l=strlen(s),x=1,c;
    for (int i=0;i<l;i++){
        c=s[i]-'0';
        if (a[x][c])x=a[x][c];
        else x=a[x][c]=++cnt;
        sum[x]++;
    }
    pos=x;
}
inline void build_fail(){
    int t=0,w=1,x;
    q[1]=1; p[1]=0;
    while (t<w){
        x=q[++t];
        for (int i=0;i<26;i++)
            if (a[x][i]){
                int k=p[x];
                while (!a[k][i]) k=p[k];
                p[a[x][i]]=a[k][i];
                q[++w]=a[x][i];
            }
    }
    for (int i=w;i;i--) sum[p[q[i]]]+=sum[q[i]];
}
int main(){
    freopen("magic.in","r",stdin);
    freopen("magic.out","w",stdout);
    scanf("%d",&n);
    for(int i=0;i<26;i++)a[0][i]=1;
    insert(pos[0]);
    build_fail();
    for (int i=1;i<=n;i++) printf("%d ",sum[pos[i]] - 1);
    cout << endl;
    return 0;
}
    原文作者:Trie树
    原文地址: https://blog.csdn.net/lemonoil/article/details/76164446
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞