后缀平衡树 nbut1653 String in the tree

传送门:点击打开链接

题意:告诉你一棵树,每个节点有一个字母,然后要求分别输出每个节点到根节点这条路径上的不重复的子串个数。

思路:因为是树形结构,我们就没办法用后缀数组搞了,必须要想办法能动态维护就好啦

我们这里用后缀平衡树来做到动态维护SA

首先我们要明确这道题的思路,每增加一个节点,根据后缀数组的知识我们知道,会多增加l-max(H[i],H[i+1])个子串,H表示高度数组,也就是两个相邻的SA的最长前缀

问题就出来了,我们如何来动态维护SA

我们用一个set来搞,set相当于一颗平衡树,里面是按照SA排序好的,现在我们来增加一个节点,我们要在字符串的前面增加这个节点,这样就相当于后缀都不变,我又多增加了一条串,因为现在set里面是有序的,所以我只要能做到比较后缀的字典序大小关系,我就能够快速把它插入到set中去。

比较后缀的字典序大小,我们用经典的二分+hash求lcp的方法来搞定

不得不说字符串hash如果都用unsigned long long来存的话还真方便,减法乘法啥的都不需要取模~

#include<map>
#include<set>
#include<cmath>
#include<ctime>
#include<stack>
#include<queue>
#include<cstdio>
#include<cctype>
#include<string>
#include<vector>
#include<cstring>
#include<iomanip>
#include<iostream>
#include<algorithm>
#include<functional>
#define fuck(x) cout<<"["<<x<<"]"
#define FIN freopen("input.txt","r",stdin)
#define FOUT freopen("output.txt","w+",stdout)
using namespace std;
typedef long long LL;
typedef pair<int, int>PII;

const int MX = 2e4 + 5;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;

const int seed = 131;
typedef unsigned long long ULL;

struct Edge {
    int v, nxt;
} E[MX];
int Head[MX], rear;
void edge_init() {
    rear = 0;
    memset(Head, -1, sizeof(Head));
}
void edge_add(int u, int v) {
    E[rear].v = v;
    E[rear].nxt = Head[u];
    Head[u] = rear++;
}

int n;
char S[MX];
ULL Hash[MX], F[MX];
LL ans[MX];

bool check(int u, int v, int m) {
    ULL h1 = Hash[u] - Hash[u - m] * F[m];
    ULL h2 = Hash[v] - Hash[v - m] * F[m];
    return h1 == h2;
}
int lcp(int u, int v) {
    int l = 0, r = min(u, v), m;
    while(l <= r) {
        m = (l + r) >> 1;
        if(check(u, v, m)) l = m + 1;
        else r = m - 1;
    }
    return l - 1;
}

struct cmp {
    bool operator()(const int &a, const int &b) {
        int p = lcp(a, b);
        if(a == p && b != p) return true;
        if(b == p && a != p) return false;
        int u = Hash[a - p] - Hash[a - p - 1] * seed;
        int v = Hash[b - p] - Hash[b - p - 1] * seed;
        return u < v;
    }
};
set<int, cmp>W;

void DFS(int u, int f, int l, LL s, ULL h) {
    h = h * seed + S[u]; Hash[l] = h;
    set<int, cmp>::iterator e = W.insert(l).first, e1 = e, e2 = e;
    int Max = 0;

    if(e1 != W.begin()) e1--, Max = max(Max, lcp(l, *e1));
    if(++e2 != W.end()) Max = max(Max, lcp(l, *e2));
    s += l - Max; ans[u] = s;
    for(int i = Head[u]; ~i; i = E[i].nxt) {
        int v = E[i].v;
        if(v == f) continue;
        DFS(v, u, l + 1, s, h);
    }
    W.erase(l);
}

int main() {
    F[0] = 1; //FIN;
    for(int i = 1; i < MX; i++) {
        F[i] = F[i - 1] * seed;
    }

    int T; scanf("%d", &T);
    while(T--) {
        W.clear();
        edge_init();

        scanf("%d", &n);
        for(int i = 1; i <= n - 1; i++) {
            int u, v;
            scanf("%d%d", &u, &v);
            edge_add(u, v);
            edge_add(v, u);
        }
        scanf("%s", S + 1);
        DFS(1, -1, 1, 0, 0);
        for(int i = 1; i <= n; i++) {
            printf("%I64d\n", ans[i]);
        }
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值