bzoj 3926 [Zjoi2015]诸神眷顾的幻想乡

资瓷点此阅读QvQ

Descripiton

给定一棵树,每个节点有一个字符,求从一个节点出发沿最短路径走到另一个节点所构成的字符串一共有多少种
注意:叶子数 20

Solution

这个题,发现叶子数很少就很好做了,以每个叶子为根节点建立SAM即可,这样原树上所有路径都在SAM出现过,统计即可。

Code

#include <bits/stdc++.h>
using namespace std;
const int N = 100005 * 2 * 20, M = 10;
typedef long long LL;
#define pb push_back
struct SAM {
    int tot, last, step[N], g[N], par[N], son[N][M], cnt[N], Q[N], ch[N], f[N];
    void init() {
        tot = 0;
        memset(par, 0, sizeof(par));
        par[0] = -1;
        memset(son, 0, sizeof(son));
    }
    int add(int last, int x) {
        int p = last, np = ++tot;
        step[np] = step[p] + 1, last = np, ++g[np];//right
        ch[np] = x;
        for (; !son[p][x] && ~p; p = par[p])    son[p][x] = np;
        if (p == -1)    return np;
        int q = son[p][x];
        if (step[q] == step[p] + 1) par[np] = q;
        else {
            step[++tot] = step[p] + 1;
            ch[tot] = x;
            int nq = tot;
            memcpy(son[nq], son[q], sizeof(son[q]));
            par[nq] = par[q];
            par[np] = par[q] = nq;
            for (; son[p][x] == q && ~p; p = par[p])    son[p][x] = nq;
        }
        return np;
    }
    void topo() {
        for (int i = 1; i <= tot; ++i)    ++cnt[step[i]];
        for (int i = 1; i <= tot; ++i)    cnt[i] += cnt[i - 1];
        for (int i = 1; i <= tot; ++i)    Q[cnt[step[i]]--] = i;        
    }
    LL gao() {
        LL ans = 0;
        for (int i = 1; i <= tot; ++i)  ans += step[i] - step[par[i]];//i状态表示多少个字符串
        return ans;
    }
}S;
int a[100005], d[100005];
vector<int> g[100005];
void dfs(int u, int fa, int last) {
    int t = S.add(last, a[u]);
    for (int i = 0; i < g[u].size(); ++i) {
        int v = g[u][i];
        if (v == fa)    continue;
        dfs(v, u, t);
    }
}
int main() {
    S.init();
    int n, c;
    scanf("%d%d", &n, &c);
    for (int i = 1; i <= n; ++i)    scanf("%d", &a[i]);
    for (int i = 1, u, v; i < n; ++i) {
        scanf("%d%d", &u, &v);
        g[u].pb(v), g[v].pb(u);
        ++d[u], ++d[v];
    }
    for (int i = 1; i <= n; ++i)
        if (d[i] == 1)  dfs(i, 0, 0);
    printf("%lld\n", S.gao());
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值