字符串树「JSOI2015」

【题目描述】
萌萌买了一颗字符串树的种子,春天种下去以后夏天就能长出一棵很大的字符串树。字符串树很奇特,树枝上都密密麻麻写满了字符串,看上去很复杂的样子。
字符串树本质上还是一棵树,即N个节点N-1条边的连通无向无环图,节点从1到N编号。与普通的树不同的是,树上的每条边都对应了一个字符串。萌萌和JYY在树下玩的时候,萌萌决定考一考JYY。每次萌萌都写出一个字符串S和两个节点U,V,需要JYY立即回答U和V之间的最短路径(即,之间边数最少的路径。由于给定的是一棵树,这样的路径是唯一的)上有多少个字符串以为前缀。
JYY虽然精通编程,但对字符串处理却不在行。所以他请你帮他解决萌萌的难题。

【输入格式】
输入第一行包含一个整数N,代表字符串树的节点数量。
接下来N-1行,每行先是两个数U,V,然后是一个字符串S,表示节点和U节点V之间有一条直接相连的边,这条边上的字符串是S。输入数据保证给出的是一棵合法的树。
接下来一行包含一个整数Q,表示萌萌的问题数。
接来下Q行,每行先是两个数U,V,然后是一个字符串S,表示萌萌的一个问题是节点U和节点V之间的最短路径上有多少字符串以S为前缀。

【输出格式】
输出Q行,每行对应萌萌的一个问题的答案。

题解
前置知识点: 可持久化Trie 树链剖分/LCA

可持久化Trie支持查找一段区间内的所有字符串的相关信息
在此题中可以树链剖分后查询路径上的字符串有多少个包含有询问的前缀
码量稍大 其实就是道模板题 时间复杂度\(O(q\ log\ n)\)

也可以直接用LCA做 免去了树剖 建Trie树时令以当前节点i代表的树从历史版本fa[i]转移过来 然后询问x~y的路径就用 树x + 树y - 树lca 就行了 实现更简单一点

代码

#include <bits/stdc++.h>
using namespace std;

inline int read() {
    int x = 0, f = 1; char ch = getchar();
    for (; ch > '9' || ch < '0'; ch = getchar()) if (ch == '-') f = -1;
    for (; ch <= '9' && ch >= '0'; ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ '0');
    return x * f;
} 

int n, m, tot;
int head[100005], pre[200005], to[200005], len[200005], sz;
char s[200005][11], q[11];

inline int o(char ch) { return ch - 'a' + 1; } 

inline void addedge(int u, int v) {
    pre[++sz] = head[u]; head[u] = sz; to[sz] = v;
    pre[++sz] = head[v]; head[v] = sz; to[sz] = u;
}

namespace Trie{
    struct trie{
        int son[30], sz;
    } tr[5000005];
    int rt[100005];

    void insert(int d, int &ind, int lst, char *str) {
        ind = ++tot; tr[ind].sz = tr[lst].sz;
        if (str[d] < 'a' || str[d] > 'z') {
            tr[ind].sz++;
            return;
        }
        for (int i = 1; i <= 26; i++) {
            tr[ind].son[i] = tr[lst].son[i];
        }
        insert(d+1, tr[ind].son[o(str[d])], tr[lst].son[o(str[d])], str);
        for (int i = 1; i <= 26; i++) {
            tr[ind].sz += tr[tr[ind].son[i]].sz - tr[tr[lst].son[i]].sz;
        }
    }

    int query(int d, int lind, int rind, char *str) {
        if (str[d] < 'a' || str[d] > 'z') {
            return max(0, tr[rind].sz - tr[lind].sz);
        } else return query(d+1, tr[lind].son[o(str[d])], tr[rind].son[o(str[d])], str);
    }
}

using namespace Trie;

namespace Treechains{
    int d[100005], dfn[100005], rnk[100005], tme, fa[100005], top[100005], siz[100005], son[100005], sonind[100005];

    void dfs(int x, int fafa) {
        siz[x] = 1;
        for (int i = head[x]; i; i = pre[i]) {
            int y = to[i]; if (y == fafa) continue;
            d[y] = d[x] + 1; fa[y] = x;
            dfs(y, x); siz[x] += siz[y];
            if (!son[x] || siz[y] > siz[son[x]]) son[x] = y, sonind[x] = i;
        }
    }

    void dfs2(int x, int tp) {
        dfn[x] = ++tme; rnk[tme] = dfn[x]; top[x] = tp;
        if (son[x]) {
            insert(1, rt[tme+1], rt[tme], s[sonind[x]]);
            dfs2(son[x], tp);
        }
        for (int i = head[x]; i; i = pre[i]) {
            int y = to[i]; if (y == fa[x] || y == son[x]) continue;
            insert(1, rt[tme+1], rt[tme], s[i]);
            dfs2(y, y);
        }
    }

    inline int query_path(int x, int y) {
        int ret = 0;
        while (top[x] != top[y]) {
            if (d[top[x]] < d[top[y]]) swap(x, y);
            ret += query(1, rt[dfn[top[x]]-1], rt[dfn[x]], q);
            x = fa[top[x]];
        }
        if (d[x] > d[y]) swap(x, y);
        ret += query(1, rt[dfn[x]], rt[dfn[y]], q);
        return ret;
    }
}

using namespace Treechains;

int main() {
    n = read();
    for (int i = 1; i < n; i++) {
        addedge(read(), read());
        scanf("%s", s[sz-1]+1); len[sz-1] = len[sz] = strlen(s[sz-1]+1);
        for (int j = 1; j <= len[sz-1]; j++) s[sz][j] = s[sz-1][j];
    }
    dfs(1, 0); dfs2(1, 1);
    m = read();
    for (int i = 1; i <= m; i++) {
        int x = read(), y = read();
        scanf("%s", q+1);
        query_path(x, y);
        printf("%d\n", query_path(x, y));
    }
    return 0;
}

原文地址:https://www.cnblogs.com/ak-dream/p/AK_DREAM29.html

时间: 2024-10-09 18:34:21

字符串树「JSOI2015」的相关文章

「JSOI2015」salesman

「JSOI2015」salesman 传送门 显然我们为了使收益最大化就直接从子树中选大的就好了. 到达次数的限制就是限制了可以选的子树的数量,因为每次回溯上来都会减一次到达次数. 多种方案的判断就是看自己选中的子树中和没选的子树中是否存在两个值相等的,这样它们就可以通过互换来达到另一种方案,值得注意的是如果选了一个值为 \(0\) 的子树就肯定可以多一种方案出来,因为这颗子树选或不选都是满足最优的. 这里有个小问题:交到BZOJ上面去它会提示你 sort 没有声明,此时需要 #include

「JSOI2015」套娃

「JSOI2015」套娃 传送门 考虑贪心. 首先我们假设所有的套娃都互相不套. 然后我们考虑合并两个套娃 \(i\),\(j\) 假设我们把 \(i\) 套到 \(j\) 里面去,那么就可以减少 \(b_j \times out_i\) 的花费. 我们有一种 贪心策略就是说把所有套娃按 \(b\) 从大到小排序,然后每次找一个 \(out\) 最大的让它套. 我们可以这么证明正确性: 对于四个套娃 \(i, j, k, l\) ,假设 \(b_i > b_j, out_k > out_l\)

「JSOI2015」圈地

「JSOI2015」圈地 传送门 显然是最小割. 首先对于所有房子,权值 \(> 0\) 的连边 \(s \to i\) ,权值 \(< 0\) 的连边 \(i \to t\) ,然后对于所有的墙,连两条边,连接起墙两边的房子,容量就是修墙的费用,然后直接用权值和 - 最小割就是最大收益. 参考代码: #include <cstring> #include <cstdio> #define rg register #define file(x) freopen(x&qu

「JSOI2015」串分割

「JSOI2015」串分割 传送门 首先我们会有一个贪心的想法:分得越均匀越好,因为长的绝对比短的大. 那么对于最均匀的情况,也就是 \(k | n\) 的情况,我们肯定是通过枚举第一次分割的位置,然后每一段长度 \(\frac{n}{k}\) 最后取最小的. 把这个思想运用到一般情况:如果分出来两段长短不一,那么长的只会比短的那个长度多 \(1\) ,再仔细想想,所有段只会有两种不同的长度 \(\lfloor \frac{n}{k} \rfloor, \lceil \frac{n}{k} \r

4477: [Jsoi2015]字符串树

4477: [Jsoi2015]字符串树 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 156  Solved: 69[Submit][Status][Discuss] Description 萌萌买了一颗字符串树的种子,春天种下去以后夏天就能长出一棵很大的字 符串树.字符串树很奇特,树枝上都密密麻麻写满了字符串,看上去很复杂的样 子. [问题描述] 字符串树本质上还是一棵树,即N个节点N-1条边的连通无向无环图,节点 从1到N编号.与普通的树不

「模板」 树套树

「模板」 树套树 <题目链接> 线段树套 SBT. 有生以来写过的最长代码. 虽然能过,但我删除 SBT 点的时候没回收内存!写了就 RE! 先放上来吧,回收内存调出来了再修改qwq. #include <algorithm> #include <climits> #include <cstdio> using std::max; using std::min; const int MAXN=50010; int n,m; class SegmentTree

「模板」 线段树——区间乘 &amp;&amp; 区间加 &amp;&amp; 区间求和

「模板」 线段树--区间乘 && 区间加 && 区间求和 <题目链接> 原来的代码太恶心了,重贴一遍. #include <cstdio> int n,m; long long p; class SegmentTree { private: struct Node { int l,r; long long v,mul,add; Node *c[2]; Node(int l,int r):l(l),r(r),mul(1LL),add(0LL) { c[

「ZJOI2017」树状数组

「ZJOI2017」树状数组 以下均基于模2意义下,默认\(n,m\)同阶. 熟悉树状数组的应该可以发现,这题其实是求\(l-1\)和\(r\)位置值相同的概率. 显然\(l=1\)的情况需要特盘. 大暴力 对于\(l=1\)的情况,可以发现一个操作不会产生影响当且仅当增加\(r\)的值,而其他情况会改变\(l-1\)或\(r\). 对于\(l!=1\)的情况: ? 针对一次修改区间\([ql,qr]\). \([ql,qr]\)包含\(l-1,r\),那么有\(\displaystyle 2

「luogu3380」【模板】二逼平衡树(树套树)

「luogu3380」[模板]二逼平衡树(树套树) 传送门 我写的树套树--线段树套平衡树. 线段树上的每一个节点都是一棵 \(\text{FHQ Treap}\) ,然后我们就可以根据平衡树的基本操作以及线段树上区间信息可合并的性质来实现了,具体细节看代码都懂. 参考代码: #include <algorithm> #include <cstdlib> #include <cstdio> #define rg register #define file(x) freo