BZOJ 3926 ZJOI2015 诸神眷顾的幻想乡 广义后缀自动机

题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=3926

 

题意概述:给出一棵N个结点的树,树上最多有20个叶子,每个点有颜色,编号0~9。求树上不同的颜色路径(由一个点到另一个点的路径上的点的颜色按顺序排列而成)数量。N<=100000.

 

当你发现只有20个叶子的时候你就很开心了因为你发现可以乱搞了。

使用秘技——广义后缀自动机来节省空间(实际上广义后缀自动机是对一棵trie做的自动机,解决多母串问题)。可以直接把原树分别以20个结点为根看成20棵trie,弄到SAM里面去。在树的分支处就让这个结点来充当所有儿子的last,仔细一想你发现SAM的性质并没有改变,并且trie的不同分支之间没有影响(说白了广义后缀自动机就是一个能够识别trie中某个结点开始的所有后缀的自动机)。

统计答案的时候直接用每个状态的MAX减去其parent的MAX就可以得到答案。

 

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cstdlib>
 5 #include<algorithm>
 6 #include<cmath>
 7 #include<queue>
 8 #include<set>
 9 #include<map>
10 #include<vector>
11 #include<cctype>
12 using namespace std;
13 const int MAXN=100005;
14 typedef long long LL;
15 
16 int N,C,c[MAXN];
17 struct edge{ int to,next; }E[MAXN<<1];
18 int first[MAXN],np;
19 struct SAM{
20     static const int maxn=4000005;
21     static const int sigma_sz=10;
22     int sz,to[maxn][sigma_sz],mx[maxn],pa[maxn];
23     SAM(){ sz=1; }
24     int newnode(){
25         memset(to[++sz],0,sizeof(to[sz]));
26         mx[sz]=pa[sz]=0;
27         return sz;
28     }
29     int extend(int w,int p){
30         int np=newnode();
31         mx[np]=mx[p]+1;
32         while(p&&!to[p][w]) to[p][w]=np,p=pa[p];
33         if(!p) pa[np]=1;
34         else{
35             int q=to[p][w];
36             if(mx[q]==mx[p]+1) pa[np]=q;
37             else{
38                 int nq=newnode(); mx[nq]=mx[p]+1;
39                 memcpy(to[nq],to[q],sizeof(to[nq]));
40                 pa[nq]=pa[q];
41                 pa[q]=pa[np]=nq;
42                 while(p&&to[p][w]==q) to[p][w]=nq,p=pa[p];
43             }
44         }
45         return np;
46     }
47     LL calc(){
48         LL re=0;
49         for(int i=2;i<=sz;i++) re+=mx[i]-mx[pa[i]];
50         return re;
51     }
52 }sam;
53 
54 void _scanf(int &x)
55 {
56     x=0;
57     char ch=getchar();
58     while(ch<'0'||ch>'9') ch=getchar();
59     while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
60 }
61 void add_edge(int u,int v)
62 {
63     E[++np]=(edge){v,first[u]};
64     first[u]=np;
65 }
66 void data_in()
67 {
68     _scanf(N);_scanf(C);
69     for(int i=1;i<=N;i++) _scanf(c[i]);
70     int x,y;
71     for(int i=1;i<N;i++){
72         _scanf(x);_scanf(y);
73         add_edge(x,y); add_edge(y,x);
74     }
75 }
76 void DFS(int i,int f,int p)
77 {
78     int pp=sam.extend(c[i],p);
79     for(int p=first[i];p;p=E[p].next){
80         int j=E[p].to;
81         if(j==f) continue;
82         DFS(j,i,pp);
83     }
84 }
85 void work()
86 {
87     for(int i=1;i<=N;i++)
88         if(!E[first[i]].next) DFS(i,0,1);
89     cout<<sam.calc()<<'\n';
90 }
91 int main()
92 {
93     data_in();
94     work();
95     return 0;
96 }

 

转载于:https://www.cnblogs.com/KKKorange/p/8529624.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值