BZOJ2754[SCOI2012]喵星球上的点名

题目大意

给出 n n 个人的姓和名,同时给出 m 段字符串。

对于每一段字符串询问其出现在多少个人的姓或者名里(一个人出现多次只计算一次)

最后询问每一个人在给出所有字符串之后包含了多少段字符串(同一段字符串包含多次只计算一次)

数据范围

1n2×104,1m5×104,1Si2×105 1 ≤ n ≤ 2 × 10 4 , 1 ≤ m ≤ 5 × 10 4 , 1 ≤ ∑ ∣ S i ∣≤ 2 × 10 5

思路

现在考虑第一个询问,观察一下,COCI2015 Divljak,我们考虑在 fail f a i l 上查询,那么一段字符串 S S 出现在多少个母串中的意思就是 S 的末尾节点的子树中有多少个母串的节点(多个来自同一母串的节点只计算一次),也就是子树查询

我们把所有的串全部插到 trie t r i e 树中建出 fail f a i l 树,考虑一个母串会对哪些查询串作出贡献,在 trie t r i e 树上的每一个节点往根的路径的并集,那么我们要维护的就是路径的并集权值加 1 考虑 dfs d f s 序维护信息,把所有插入时遍历的 trie t r i e 树节点拿出来,按照 dfs d f s 序排序之后,将所有拿出来的点的权值加 1,相邻两个点的 lca l c a 处的权值减去重复计算的 1

维护好信息之后,对于每一个查询串在树状数组中子树查询一下即可。

现在考虑第二个询问,我们要查的就是姓名串在 fail f a i l 树上所有点往根路径的并集,其中如果是 查询串的结尾,那么该点的值为 1

类似上面的方法,先预处理出 fail f a i l 树上每一个节点到根的路径权值和,把所有姓名串插入时遍历的节点拿出来,按照 dfs d f s 序排序之后,加上所有点的权值和,再减去相邻两点之间 lca l c a 的权值和即可

PE有点无语

#include <cstdio>
#include <vector>
#include <map>
#include <cstring>
#include <algorithm>

using std :: vector;
using std :: map;

template <typename Tp>Tp Max(const Tp &a, const Tp &b) {return a > b ? a : b;}
template <typename Tp>Tp Min(const Tp &a, const Tp &b) {return a < b ? a : b;}

template <typename Tp>void Read(Tp &x) {
    Tp in = 0, f = 1; char ch = getchar();
    while(ch<'0' || ch>'9') {if(ch=='-') f = -1; ch = getchar();}
    while(ch>='0' && ch<='9') {in = in * 10 + ch - '0'; ch = getchar();}
    x = in * f;
}

const int SN = 200000 + 10;

map<int,int> ch[SN];

vector<int> S[SN], A1[SN], A2[SN], A3[SN];

int fail[SN], sum[SN], que[SN], pre[SN], net[SN], P[SN][2], head[SN], is_end[SN], val[SN];

int n, m, len, CLOCK, TIME, cnt, num;

int A[SN], s[SN];

struct Edge {
    int v, next;
}E[SN];

void Add_E(int u, int v) {
    E[++num].v = v, E[num].next = head[u], head[u] = num;
}

namespace Tree {

    int d[SN], size[SN], son[SN], fa[SN], top[SN];

    void Dfs(int u) {
    size[u] = 1;

    for(int i = head[u]; i; i = E[i].next) {
        int to = E[i].v;
        if(to == fa[u]) continue ;
        fa[to] = u, d[to] = d[u] + 1;
        Dfs(to);
        size[u] += size[to];
        if(size[to] >= size[son[u]] || !son[u]) son[u] = to;
    }

    }

    void Dfs(int u, int t) {
    top[u] = t;

    if(son[u]) Dfs(son[u], t);
    else return ;

    for(int i = head[u]; i; i = E[i].next) {
        int to = E[i].v;
        if(to != son[u] && to != fa[u]) Dfs(to, to);
    }

    }

    int LCA(int x, int y) {
    while(top[x] != top[y]) {
        if(d[top[x]] > d[top[y]]) std :: swap(x, y);
        y = fa[top[y]];
    }
    return d[x] < d[y] ? x : y;
    }

}

void Insert(int len, int num, int flag) {

    int now = 0, to;

    for(int i = 1; i <= len; i++) {
    to = s[i];
    if(!ch[now][to]) S[now].push_back(to), ch[now][to] = ++TIME;
    now = ch[now][to];
    }

    is_end[num] = now;

    if(flag) val[now]++;
}

void Getfail() {

    int front = 1, tail = 0;

    vector<int> :: iterator it;

    for(it = S[0].begin(); it != S[0].end(); it++)
    Add_E(0, ch[0][(*it)]), que[++tail] = ch[0][(*it)];

    while(front <= tail) {

    int now = que[front++], u;

    for(it = S[now].begin(); it != S[now].end(); it++) {
        u = ch[now][(*it)];
        que[++tail] = u;
        int v = fail[now];
        while(v && !ch[v][(*it)]) v = fail[v];
        fail[u] = ch[v][(*it)], Add_E(fail[u], u);
    }
    }

}

void Dfs(int u, int fa) {

    pre[u] = ++CLOCK;

    for(int i = head[u]; i; i = E[i].next) {
    int to = E[i].v;
    if(to == fa) continue ;
    Dfs(to, u);
    }

    net[u] = CLOCK;
}

void Add(int x, int C) {
    if(x == 0) return ;
    while(x <= CLOCK) sum[x] += C, x += x & -x;
}

int Ask(int x) {
    int res = 0;
    while(x) res += sum[x], x -= x & -x;
    return res;
}

bool cmp(int a, int b) {
    return pre[a] < pre[b];
}

void Solve(int u, int fa) {

    for(int i = head[u]; i; i = E[i].next) {
    int to = E[i].v;
    if(to == fa) continue ;
    sum[to] = sum[u] + val[to];
    Solve(to, u);
    }

}

int main() {
    int x, y;

    Read(n), Read(m);

    for(int i = 1; i <= n; i++) {
    Read(x);

    for(int j = 1; j <= x; j++) Read(s[j]), A1[i].push_back(s[j]);

    Insert(x, ++cnt, 0), P[i][0] = cnt;

    Read(x);

    for(int j = 1; j <= x; j++) Read(s[j]), A2[i].push_back(s[j]);

    Insert(x, ++cnt, 0), P[i][1] = cnt;
    }

    for(int i = 1; i <= m; i++) {
    Read(x);
    for(int j = 1; j <= x; j++) Read(s[j]), A3[i].push_back(s[j]);
    Insert(x, ++cnt, 1), P[n + i][0] = cnt;
    }

    Getfail();

    Dfs(0, -1);

    Tree :: Dfs(0);

    Tree :: Dfs(0, 0);

    vector<int> :: iterator it;

    for(int i = 1; i <= n; i++) {

    int now = 0;

    A[0] = 0;

    for(it = A1[i].begin(); it != A1[i].end(); it++)
        now = ch[now][(*it)], A[++A[0]] = now;

    now = 0;

    for(it = A2[i].begin(); it != A2[i].end(); it++)
        now = ch[now][(*it)], A[++A[0]] = now;

    std :: sort(A + 1, A + A[0] + 1, cmp);

    A[0] = std :: unique(A + 1, A + A[0] + 1) - A - 1;

    for(int i = 1; i <= A[0]; i++)
        Add(pre[A[i]], 1);

    for(int i = 2; i <= A[0]; i++)
        Add(pre[Tree :: LCA(A[i], A[i - 1])], -1);
    }

    for(int i = 1; i <= m; i++) 
    printf("%d\n", Ask(net[is_end[P[n + i][0]]]) - Ask(pre[is_end[P[n + i][0]]] - 1));

    memset(sum, 0, sizeof sum);

    Solve(0, -1);

    for(int i = 1; i <= n; i++) {

    int now = 0, ans = 0;

    A[0] = 0;

    for(it = A1[i].begin(); it != A1[i].end(); it++) 
        now = ch[now][(*it)], A[++A[0]] = now;

    now = 0;

    for(it = A2[i].begin(); it != A2[i].end(); it++)
        now = ch[now][(*it)], A[++A[0]] = now;

    std :: sort(A + 1, A + A[0] + 1, cmp);

    A[0] = std :: unique(A + 1, A + A[0] + 1) - A - 1;

    for(int j = 1; j <= A[0]; j++)
        ans += sum[A[j]];

    for(int j = 2; j <= A[0]; j++)
        ans -= sum[Tree :: LCA(A[j], A[j - 1])];

    if(i == 1) printf("%d", ans);
    else printf(" %d", ans);
    }

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值