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

来源:互联网 发布:beta算法 编辑:程序博客网 时间:2024/06/10 18:21

魔法串(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;}
原创粉丝点击