练习赛 魔法串 (补全AC自动机 可持久化树)

来源:互联网 发布:中国农业网络书屋 编辑:程序博客网 时间:2024/05/19 15:43

魔法串

题目描述
给你一棵n+1个结点的有根树,结点从0到n标号,其中0为根结点。
这是一棵魔法树。这棵树的每条边有一个魔力值,同一个结点连向不同子结点的边的魔力值不同。一个结点所代表的魔法串是从根一直走到这个结点,经过的魔力值依次排列形成的有序序列,另外,一个串是魔法串当且仅当它被一个结点所代表。
现在,为了使用强大的魔法,你需要对每个魔法串,找到最长的是它后缀的魔法串。为了方便输出,你只需要输出代表这个魔法串的结点的标号即可。若没有这个魔法串,请输出0。

输入格式
第一行一个整数n,代表除根以外的结点个数。
第二行 n个整数,第i个整数P_i代表标号为i的结点的父亲标号。
第三行 n个整数,第i个整数C_i代表标号为i的结点连向父亲的边的魔力值。

输出格式
输出一行n个整数,第i个整数表示第i个结点代表的魔法串的答案。

样例输入
7
0 0 1 1 2 4 5
1 2 3 2 1 1 3

样例输出
0 0 02 1 5 3

数据范围与约定
对于30%的数据,保证1<=n<=2000。
对于100%的数据,保证1<=n<=200000,0<=P _ i < i,1<=C _ i<=n。

思路:
前30%的数据:考虑AC自动机的思路,我们要求的答案相当于是求每个点的fail。直接用AC自动机做,用map存储转移数组。

前100%的数据:上一个方法之所以T掉,是因为我们找fail的时候如果找不到会继续跳fa的fail,而这里C _ i<=n,也就是说一个点的子节点由26变为了200000,炸飞!
于是补全AC自动机(Trie图),考虑一个结点u所连出的转移边与fail[u]所连出的转移边的关系,只有u直接连出的边会影响这些转移边,而边数是n-1条。于是我们考虑将fail[u]的转移边全部复制给u,再在此基础上对u的转移边进行修改。
这个如何实现?用可持久化线段树维护每个结点的转移边即可。

#include<cstdio>#include<cstring>#include<algorithm>using namespace std;#define N 200010#define M 6000010int fail[N], q[N], root[N], fat[N], c[N], head[N];int idc=0, tot, n;struct Edge {int next, to, w;}ed[N];struct Node {int l, r, to;}t[M];void adde(int u, int v, int w){    ed[++idc].to = v;    ed[idc].next = head[u];    ed[idc].w = w;    head[u] = idc; }void modify(int &x, int l, int r, int val, int v){    int y=x; x=++tot; t[x] = t[y];//新开一个树节点,只有root~val的一条链修改了,其他的就指向fail的等位     //相当于每次修改都只新开了一条链的空间,其他不变的就指向fail的等位     if(l == r){        t[x].to = v;//保存此点在原树中的位置,使得找fail时能快速返回它的位置(代表这个魔法串的结点的标号)         return;//直接覆盖更新为最近的     }    int mid = (l + r) >> 1;    if(val <= mid) return modify(t[x].l, l, mid, val, v);//按照魔法值查找     else return modify(t[x].r, mid+1, r, val, v);}int query(int x, int l, int r, int val){    if(l == r) return t[x].to;    int mid = (l + r) >> 1;    if(val <= mid) return query(t[x].l, l, mid, val);     return query(t[x].r, mid+1, r, val);}int main(){    freopen("magic.in", "r", stdin);    freopen("magic.out", "w", stdout);    scanf("%d", &n);    for(int i=1; i<=n; i++) scanf("%d", &fat[i]);    for(int i=1; i<=n; i++) scanf("%d", &c[i]), adde(fat[i], i, c[i]);    //用给出的关系可以直接建一棵树,以魔法值作为点权(类似AC自动机)     int l=0, r=0; q[r++] = 0;//0为根节点     for(; l<r; l++){//bfs补全找fail         int u = q[l];        root[u] = root[fail[u]];//若无子节点就跟fail相同         for(int i=head[u]; i; i=ed[i].next){            fail[q[r++] = ed[i].to] = query(root[fail[u]], 1, n, ed[i].w);//fail[u]已经补全了(有所有子节点)            //在root[fail[u]]引领的值域线段树(魔法值为下标,维护在原树中的位置)中查找ed[i].w。            //找到之后得到地址t[x].to(因为补全了所以一定能找到)            //t[x].to存的是这个值在原树上的位置(因为是bfs更新的所以存的一定是最近的是它后缀的位置,也就是fail)             modify(root[u], 1, n, ed[i].w, ed[i].to);//现在就来更新这棵树(本来root[u] = root[fail[u]]是同一棵树)            //遍历它所有真实的子节点,(每一个都有可能成为其他点的fail)            //这样的话我们在找u的fail的时候,先找到了fa[u].fail,然后在它的值域线段树中查询与u相等的节点的位置            //如果fa[u].fail真实有这个节点自然就找到了(所有真实子节点都存在树中)            //如果fa[u].fail并没有这个节点那么我们找到的就是fa[u].fail.fail...若干个fail的相同子节点的位置             //这样就解决了不能一次找到fail而超时的问题(可以一次找到)         }    }    for(int i=1; i<=n; i++) printf("%d ", fail[i]);    return 0;}