魔法串(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;
}