• BZOJ3926:[ZJOI2015]诸神眷顾的幻想乡(广义SAM)


    Description

     幽香是全幻想乡里最受人欢迎的萌妹子,这天,是幽香的2600岁生日,无数幽香的粉丝到了幽香家门前的太阳花田上来为幽香庆祝生日。 

    粉丝们非常热情,自发组织表演了一系列节目给幽香看。幽香当然也非常高兴啦。 
    这时幽香发现了一件非常有趣的事情,太阳花田有n块空地。在过去,幽香为了方便,在这n块空地之间修建了n-1条边将它们连通起来。也就是说,这n块空地形成了一个树的结构。 
    有n个粉丝们来到了太阳花田上。为了表达对幽香生日的祝贺,他们选择了c中颜色的衣服,每种颜色恰好可以用一个0到c-1之间的整数来表示。并且每个人都站在一个空地上,每个空地上也只有一个人。这样整个太阳花田就花花绿绿了。幽香看到了,感觉也非常开心。 
    粉丝们策划的一个节目是这样的,选中两个粉丝A和B(A和B可以相同),然后A所在的空地到B所在的空地的路径上的粉丝依次跳起来(包括端点),幽香就能看到一个长度为A到B之间路径上的所有粉丝的数目(包括A和B)的颜色序列。一开始大家打算让人一两个粉丝(注意:A,B和B,A是不同的,他们形成的序列刚好相反,比如红绿蓝和蓝绿红)都来一次,但是有人指出这样可能会出现一些一模一样的颜色序列,会导致审美疲劳。 
    于是他们想要问题,在这个树上,一共有多少可能的不同的颜色序列(子串)幽香可以看到呢? 
    太阳花田的结构比较特殊,只与一个空地相邻的空地数量不超过20个。 

    Input

     第一行两个正整数n,c。表示空地数量和颜色数量。 

    第二行有n个0到c-1之间,由空格隔开的整数,依次表示第i块空地上的粉丝的衣服颜色。(这里我们按照节点标号从小到大的顺序依次给出每块空地上粉丝的衣服颜色)。 
    接下来n-1行,每行两个正整数u,v,表示有一条连接空地u和空地v的边。 

    Output

     一行,输出一个整数,表示答案。 

    Sample Input

    7 3
    0 2 1 2 1 0 0
    1 2
    3 4
    3 5
    4 6
    5 7
    2 5

    Sample Output

    30

    HINT

    对于所有数据,1<=n<=100000, 1<=c<=10。

    对于15%的数据,n<=2000。
    另有5%的数据,所有空地都至多与两个空地相邻。
    另有5%的数据,除一块空地与三个空地相邻外,其他空地都分别至多与两个空地相邻。
    另有5%的数据,除某两块空地与三个空地相邻外,其他空地都分别至多与两个空地相邻

    Solution

    广义$SAM$,也就是给一个$Trie$建$SAM$。具体操作很简单,就是$DFS$一下$Trie$树,每次将一个字符插入$SAM$的时候,将$SAM$的$last$记为这个字符在$Trie$树上的父亲的$np$就可以了。

    由于一种字符串只能做出一次贡献,所以答案为$ans=sum step[i]-step[fa[i]]$

    Code

     1 #include<iostream>
     2 #include<cstring>
     3 #include<cstdio>
     4 #define N (2000009)
     5 #define M (100009)
     6 #define LL long long
     7 using namespace std;
     8 
     9 struct  Edge{int to,next;}edge[M<<1];
    10 int head[M],num_edge;
    11 int n,m,u,v,a[M],Ind[M];
    12 
    13 void add(int u,int v)
    14 {
    15     edge[++num_edge].to=v;
    16     edge[num_edge].next=head[u];
    17     head[u]=num_edge;
    18 }
    19 
    20 struct SAM
    21 {
    22     int son[N][12],fa[N],step[N],wt[N],od[N];
    23     int p,q,np,nq,cnt;
    24     SAM(){cnt=1;}
    25 
    26     int Insert(int last,int x)
    27     {
    28         p=last; np=++cnt; step[np]=step[p]+1;
    29         while (!son[p][x] && p) son[p][x]=np, p=fa[p];
    30         if (!p) fa[np]=1;
    31         else
    32         {
    33             q=son[p][x];
    34             if (step[p]+1==step[q]) fa[np]=q;
    35             else
    36             {
    37                 nq=++cnt; step[nq]=step[p]+1;
    38                 memcpy(son[nq],son[q],sizeof(son[q]));
    39                 fa[nq]=fa[q]; fa[q]=fa[np]=nq;
    40                 while (son[p][x]==q) son[p][x]=nq, p=fa[p];
    41             }
    42         }
    43         return np;
    44     }
    45     void Calc()
    46     {
    47         LL ans=0;
    48         for (int i=1; i<=cnt; ++i)
    49             ans+=(LL)(step[i]-step[fa[i]]);
    50         printf("%lld
    ",ans);
    51     }
    52 }SAM;
    53 
    54 void DFS(int x,int fa,int pre)
    55 {
    56     int tmp=SAM.Insert(pre,a[x]);
    57     for (int i=head[x]; i; i=edge[i].next)
    58         if (edge[i].to!=fa) DFS(edge[i].to,x,tmp);
    59 }
    60 
    61 int main()
    62 {
    63     scanf("%d%d",&n,&m);
    64     for (int i=1; i<=n; ++i)
    65         scanf("%d",&a[i]);
    66     for (int i=1; i<=n-1; ++i)
    67     {
    68         scanf("%d%d",&u,&v);
    69         add(u,v); add(v,u);
    70         Ind[u]++; Ind[v]++;
    71     }
    72     for (int i=1; i<=n; ++i)
    73         if (Ind[i]==1) DFS(i,-1,1);
    74     SAM.Calc();
    75 }
  • 相关阅读:
    vim 多个文件切换
    Ruby 格式化代码 vim
    Ruby 配置vimrc
    print puts p
    开机跳过开机选择系统的选项界面
    Linux学习笔记:rm删除文件和文件夹
    Linux学习笔记:ps -ef、ps aux、kill -9
    Linux学习笔记:ctrl+z、ctrl+c、ctrl+d的区别
    Shell学习笔记:<<EOF子命令
    Linux学习笔记:crontab定时任务
  • 原文地址:https://www.cnblogs.com/refun/p/10012185.html
Copyright © 2020-2023  润新知