bzoj3926 [Zjoi2015]诸神眷顾的幻想乡

题目链接

分析:
先强调一点,题目中特意指出:太阳花田的结构比较特殊,只与一个空地相邻的空地数量不超过20个
翻译一下:最多有20个叶结点

知道这道题是用广义后缀自动机解决
广义 SAM 中包含了所有互异的子串
所以我们应该把尽量少,但是包含所有情况的字符串扔进 SAM

一开始我想的比较简单,只要把所有叶结点到根的字符串扔进去就可以了
但是这样显然包括所有的字符串情况(画个图就知道了,没法处理折线

所以最后的解决方法就是把叶结点两两配对,
得到的字符串扔进 SAM

简单的做法:枚举每一个叶结点,以此为根,得到根到结点的路径,扔进 SAM 即可

最后的答案:

ni=1dis[i]dis[fa[i]]

意思就是从 parenti 走到 i 有多少种不同的方式(parenti i 有相同的前缀)

tip

在以某一个结点为根dfs的时候,我们不用把每一个字符串记录下来再insert
我们可以在dfs的过程中,直接insert结点即可
但是要注意插入不同的结点时, last 是不一样的
所以我们需要专门记录一下

起始时: last=root

第一次写,insert操作比较容易写错

#include<cstdio>
#include<cstring>
#include<iostream>
#define ll long long

using namespace std;

const int N=100010;
int last=1,root=1,sz=1,ch[N*40][11],dis[N*40],fa[N*40];
int V[N],n,c,st[N],tot=0,in[N];
struct node{
    int y,nxt;
};
node way[N<<1];

void add(int u,int w)
{
    tot++;
    way[tot].y=w;way[tot].nxt=st[u];st[u]=tot;
    tot++;
    way[tot].y=u;way[tot].nxt=st[w];st[w]=tot;
}

void insert(int x)
{
    int now=ch[last][x],pre=last;
    if (now)
    {
        if (dis[now]==dis[pre]+1) last=now;
        else
        {
            int nows=++sz;
            dis[nows]=dis[pre]+1;
            memcpy(ch[nows],ch[now],sizeof(ch[now]));
            fa[nows]=fa[now]; fa[now]=nows;
            for (;pre&&ch[pre][x]==now;pre=fa[pre]) ch[pre][x]=nows;
            last=nows;
        }
    }
    else
    {
        now=++sz;
        dis[now]=dis[pre]+1; last=now;
        for (;pre&&!ch[pre][x];pre=fa[pre]) ch[pre][x]=now;
        if (!pre) fa[now]=root;
        else
        {
            int q=ch[pre][x];
            if (dis[q]==dis[pre]+1) fa[now]=q;
            else
            {
                int nows=++sz;
                dis[nows]=dis[pre]+1;
                memcpy(ch[nows],ch[q],sizeof(ch[q]));
                fa[nows]=fa[q]; fa[q]=fa[now]=nows;
                for (;pre&&ch[pre][x]==q;pre=fa[pre]) ch[pre][x]=nows;
            }
        }
    }
}

void dfs(int now,int fa)
{
    insert(V[now]);
    int t=last;    //我们不用从头构造,只要记录一下在SAM上走到了哪里即可 
    for (int i=st[now];i;i=way[i].nxt)
        if (way[i].y!=fa)
            dfs(way[i].y,now),last=t;
}

int main()
{
    scanf("%d%d",&n,&c);
    for (int i=1;i<=n;i++) scanf("%d",&V[i]);
    for (int i=1;i<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,y); in[x]++; in[y]++;
    }

    for (int i=1;i<=n;i++) 
        if (in[i]==1)
            last=root,dfs(i,0);

    ll ans=0;
    for (int i=1;i<=sz;i++)
        ans+=(ll)dis[i]-dis[fa[i]];
    printf("%lld\n",ans);
    return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值