[洛谷P2590][ZJOI2008]树的统计

本文详细解析了一道涉及树链剖分和线段树的经典算法题目,介绍了如何利用树链剖分和线段树实现节点权值的快速修改及路径上节点最大值与权值和的查询。

题目大意:一棵树,支持三个操作,

$CHANGE\;u\;t:$ 把结点$u$的权值改为$t$

$QMAX\;u\;v:$ 询问从点$u$到点$v$的路径上的节点的最大权值

$QSUM\;u\;v:$ 询问从点$u$到点$v$的路径上的节点的权值和

题解:裸的树链剖分

卡点:线段树区间修改我不知道哪根筋搭错了,写了$l\;==\;r$(应为$L\;\leq\;l\;\&\&\;R\;\geq\;r$)

 

C++ Code:

#include <cstdio>
#define maxn 30010
using namespace std;
const int inf = 0x3f3f3f3f;
int n, m;
int w[maxn], V[maxn << 2], M[maxn << 2];
inline int max(int a, int b) {return a > b ? a : b;}
void swap(int &a, int &b) {a ^= b ^= a ^= b;}
void update(int rt) {
    V[rt] = V[rt << 1] + V[rt << 1 | 1];
    M[rt] = max(M[rt << 1], M[rt << 1 | 1]);
}
void build(int rt, int l, int r) {
    if (l == r) {
        V[rt] = M[rt] = w[l];
        return ;
    }
    int mid = l + r >> 1;
    build(rt << 1, l, mid);
    build(rt << 1 | 1, mid + 1, r);
    update(rt);
}
void add(int rt, int l, int r, int p, int num) {
    if (l == r) {
        V[rt] = M[rt] = num;
        return ;
    }
    int mid = l + r >> 1;
    if (p <= mid) add(rt << 1, l, mid, p, num);
    else add(rt << 1 | 1, mid + 1, r, p, num);
    update(rt);
}
int askS(int rt, int l, int r, int L, int R) {
    if (L <= l && R >= r) {
        return V[rt];
    }
    int mid = l + r >> 1, ans = 0;
    if (L <= mid) ans = askS(rt << 1, l, mid, L, R);
    if (R > mid) ans = ans + askS(rt << 1 | 1, mid + 1, r, L, R);
    return ans;
}
int askM(int rt, int l, int r, int L, int R) {
    if (L <= l && R >= r) {
        return M[rt];
    }
    int mid = l + r >> 1, ans = -inf;
    if (L <= mid) ans = askM(rt << 1, l, mid, L, R);
    if (R > mid) ans = max(ans, askM(rt << 1 | 1, mid + 1, r, L, R));
    return ans;
}

int head[maxn], cnt;
struct Edge {
    int to, nxt;
} e[maxn << 1];
void addE(int a, int b) {
    e[++cnt] = (Edge) {b, head[a]}; head[a] = cnt;
}
int fa[maxn], sz[maxn], son[maxn], dep[maxn];
int top[maxn], dfn[maxn], idx;
void dfs1(int rt) {
    sz[rt] = 1;
    for (int i = head[rt]; i; i = e[i].nxt) {
        int v = e[i].to;
        if (v != fa[rt]) {
            dep[v] = dep[rt] + 1;
            fa[v] = rt;
            dfs1(v);
            if (!son[rt] || sz[v] > sz[son[rt]]) son[rt] = v;
            sz[rt] += sz[v];
        }
    }
}
void dfs2(int rt) {
    dfn[rt] = ++idx;
    int v = son[rt];
    if (v) top[v] = top[rt], dfs2(v);
    for (int i = head[rt]; i; i = e[i].nxt) {
        v = e[i].to;
        if (v != son[rt] && v != fa[rt]) {
            top[v] = v;
            dfs2(v);
        }
    }
}
int queryS(int x, int y) {
    int ans = 0;
    while (top[x] != top[y]) {
        if (dep[top[x]] < dep[top[y]]) swap(x, y);
        ans += askS(1, 1, n, dfn[top[x]], dfn[x]);
        x = fa[top[x]];
    }
    if (dep[x] > dep[y]) swap(x, y);
    ans += askS(1, 1, n, dfn[x], dfn[y]);
    return ans;
}
int queryM(int x, int y) {
    int ans = -inf;
    while (top[x] != top[y]) {
        if (dep[top[x]] < dep[top[y]]) swap(x, y);
        ans = max(ans, askM(1, 1, n, dfn[top[x]], dfn[x]));
        x = fa[top[x]];
    }
    if (dep[x] > dep[y]) swap(x, y);
    ans = max(ans, askM(1, 1, n, dfn[x], dfn[y]));
    return ans;
}
int main() {
    scanf("%d", &n);
    for (int i = 1; i < n; i++) {
        int a, b;
        scanf("%d%d", &a, &b);
        addE(a, b);
        addE(b, a);
    }
    dep[1] = 1;
    dfs1(1);
    top[1] = 1;
    dfs2(1);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &w[dfn[i]]);
    }
    build(1, 1, n);
    scanf("%d", &m);
    while (m --> 0) {
        char op[10];
        int x, y;
        scanf("%s%d%d", op, &x, &y);
        if (op[1] == 'M') {
            printf("%d\n", queryM(x, y));
        }
        if (op[1] == 'S') {
            printf("%d\n", queryS(x, y));
        }
        if (op[1] == 'H') {
            add(1, 1, n, dfn[x], y);
        }
    }
    return 0;
}

  

转载于:https://www.cnblogs.com/Memory-of-winter/p/9483053.html

### 关于洛谷 P1481 的字典 (Trie) 算法 #### 字典 (Trie) 数据结构简介 字典是一种用于高效存储和检索字符串集合的数据结构。它通过将公共前缀共享的方式来节省空间并提高查询效率。对于本题而言,字典的核心思想在于构建一棵多叉来表示一组单词的字符序列[^5]。 #### 构建字典的过程 在字典中,每个节点代表一个字符,而从根到某个节点的路径则构成了一部分字符串。以下是构建字典的主要过程: 1. **初始化**: 创建一个根节点 `root`。 2. **插入操作**: 将每一个单词逐字符插入字典中。如果当前字符不存在,则创建新的子节点;否则沿已有路径继续向下遍历直到完成整个单词的插入。 ```python class TrieNode: def __init__(self): self.children = {} self.is_end_of_word = False def insert(root, word): node = root for char in word: if char not in node.children: node.children[char] = TrieNode() node = node.children[char] node.is_end_of_word = True ``` #### 查询操作 为了判断某字符串是否存在或者统计某些特定条件下的匹配数量,可以通过递归或迭代的方式访问字典中的相应节点。例如,在此题目背景下可能需要计算满足一定约束条件下能够组成的合法串数。 #### 动态规划与状态转移方程 由于题目涉及到构造固定长度且包含指定数目关键词汇表内的任意组合形式的新字符串问题,因此除了单纯依靠字典外还需要引入动态规划的思想来进行求解。设 dp[i][j] 表示已经处理到了第 i 位,并且此时正好包含了 j 个目标词汇的情况总数,则有如下关系式成立: \[dp[i][j]=\sum_{w \in S} dp[i-len(w)][max(0,j-cnt[w])]\] 其中 \(S\) 是所有候选单词集,\(len(w)\) 和 \(cnt[w]\) 分别对应着单个词语本身的尺寸以及其贡献给最终计数值的部分大小[^4]。 #### 完整解决方案框架 综合以上分析我们可以给出这样一个完整的解决流程概述: - 初始化必要的辅助数组如 trie 结构体实例化对象; - 根据输入数据依次调用上述定义好的函数完成各项预处理工作比如建立索引映射关系等准备工作; - 使用双重循环枚举位置变量i及其关联参数k从而填充DP表格直至得出最后答案为止。 ```python MOD = int(1e9 + 7) trie_root = TrieNode() for word in words_set: insert(trie_root, word) # Initialize DP table with dimensions [N+1][K+1], where N is max length of string to be formed, # K represents number of distinct required substrings. dp = [[0]*(k_max+1) for _ in range(n_max+1)] dp[0][0] = 1 for l in range(1, n_max+1): for c in alphabet: current_node = trie_root temp_dp = list(dp[l]) # Traverse through possible prefixes ending at position 'l' prefix_length = 0 while current_node and prefix_length <= l: new_k = min(k_max, k_found[current_node]) if new_k >=0 : temp_dp[new_k]=(temp_dp[new_k]+dp[l-prefix_length][new_k-count(current_node)])% MOD if c in current_node.children: current_node=current_node.children[c] prefix_length+=1 else: break dp[l]=temp_dp[:] result=sum([d[k_target]%MOD for d in dp[n]]) print(result) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值