[bzoj3926][广义SAM]诸神眷顾的幻想乡

本文介绍了一个关于广义SAM算法的应用实例,通过构建广义后缀自动机解决了一个有趣的问题:计算在一个特定的树结构中不同颜色序列的数量。文章详细解释了如何利用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%的数据,除某两块空地与三个空地相邻外,其他空地都分别至多与两个空地相邻

题解

广义SAM裸题
题里有一个很有趣的性质:叶子节点不超过20个
我们可以把每个叶子节点都作为根,那么原树就变成了一棵“Tire”树(假装他是吧2333)。由于广义SAM可以识别Tire节点到根的所有串。再想想,能走的路径其实就是叶子节点到树内任意一点中的子串啦。这时候叶子节点作为Tire的根,树内任意一点作为Tire节点。对于这20个Tire构建广义后缀自动机,直接建在一起就可以了
广义后缀自动机:你就BFS建图,搜到一个节点的时候把last设为这个节点的父亲,然后跟狭义后缀自动机一样讨论建
最后枚举每个状态处理一下该状态下有的子串数即可

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long LL;
struct SAM
{
    int dep,parent,son[11];
}tr[4100000];int cnt,root,LAST;
int add(int x)
{
    int np=++cnt,p=LAST;
    tr[np].dep=tr[p].dep+1;
    while(p && tr[p].son[x]==0)tr[p].son[x]=np,p=tr[p].parent;
    if(p==0)tr[np].parent=root;
    else
    {
        int q=tr[p].son[x];
        if(tr[q].dep==tr[p].dep+1)tr[np].parent=q;
        else
        {
            int nq=++cnt;
            tr[nq]=tr[q];tr[nq].dep=tr[p].dep+1;
            tr[q].parent=tr[np].parent=nq;
            while(p && tr[p].son[x]==q)tr[p].son[x]=nq,p=tr[p].parent;
        }
    }
    return np;
}
struct node
{
    int x,y,next;
}a[210000];int len,last[110000];
void ins(int x,int y)
{
    len++;
    a[len].x=x;a[len].y=y;
    a[len].next=last[x];last[x]=len;
}
int n,c,col[110000],ru[110000];
struct Anode
{
    int pos,x;
}list[210000];
int head,tail;
bool vis[210000];
void add_SAM(int st)
{
    memset(vis,true,sizeof(vis));
    LAST=root;head=1;tail=2;
    vis[st]=false;
    Anode tmp;tmp.x=st;
    tmp.pos=add(col[st]);
    list[1]=tmp;head=1;tail=2;
    while(head!=tail)
    {
        tmp=list[head];
        int x=tmp.x;
        for(int k=last[x];k;k=a[k].next)
        {
            int y=a[k].y;
            if(vis[y]==true)
            {
                vis[y]=false;
                Anode num;num.x=y;LAST=tmp.pos;
                num.pos=add(col[y]);
                list[tail++]=num;
            }
        }
        head++;
    }
}
int main()
{
    len=0;memset(last,0,sizeof(last));
    scanf("%d%d",&n,&c);
    for(int i=1;i<=n;i++)scanf("%d",&col[i]);
    for(int i=1;i<n;i++)
    {
        int x,y;scanf("%d%d",&x,&y);ru[x]++;ru[y]++;
        ins(x,y);ins(y,x);
    }
    root=LAST=++cnt;
    for(int i=1;i<=n;i++)if(ru[i]==1)add_SAM(i);
    LL ans=0;
    for(int i=1;i<=cnt;i++)ans+=(LL)tr[i].dep-tr[tr[i].parent].dep;
    printf("%lld\n",ans);
    return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值