数据结构题锦——树链
树链:
树链求最近公共祖先
题目:
题目描述
如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。
输入格式
第一行包含三个正整数 N,M,S,分别表示树的结点个数、询问的个数和树根结点的序号。
接下来 N−1 行每行包含两个正整数x,y,表示 x 结点和 y 结点之间有一条直接连接的边(数据保证可以构成树)。
接下来 M 行每行包含两个正整数a,b,表示询问 a 结点和 b 结点的最近公共祖先。
输出格式
输出包含 M 行,每行包含一个正整数,依次为每一个询问的结果。
输入输出样例
输入 #15 5 4 3 1 2 4 5 1 1 4 2 4 3 2 3 5 1 2 4 5输出 #14 4 1 4 4说明/提示
对于 30% 的数据,N≤10,M≤10。
对于 70% 的数据,N≤10000,M≤10000。
对于 100% 的数据,N≤500000,M≤500000。
样例说明:
该树结构如下:
第一次询问:2,4 的最近公共祖先,故为 4。
第二次询问:3,2 的最近公共祖先,故为 4。
第三次询问:3,5 的最近公共祖先,故为 1。
第四次询问:1,2 的最近公共祖先,故为 4。
第五次询问:4,5 的最近公共祖先,故为 4。
故输出依次为 4, 4, 1, 4, 4。
分析:
LCA:
树上倍增法可以做,树链也可以做
代码:
#include <bits/stdc++.h> using namespace std; const int N = 500005, M = N << 1; int n, m, root; int w[N], h[N], e[M], ne[M], idx; int dep[N], fa[N], sz[N], son[N]; int id[N], nw[N], top[N], tot; void add(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx ++; } void dfs1(int x, int father, int depth) { dep[x] = depth, fa[x] = father, sz[x] = 1; for (int i = h[x]; ~i; i = ne[i]) { int y = e[i]; if (y == father) continue; dfs1(y, x, depth + 1); sz[x] += sz[y]; if (sz[son[x]] < sz[y]) son[x] = y; } } void dfs2(int x, int t) { id[x] = ++ tot, nw[tot] = w[x], top[x] = t; if (!son[x]) return; dfs2(son[x], t); for (int i = h[x]; ~i; i = ne[i]) { int y = e[i]; if (y == fa[x] || y == son[x]) continue; dfs2(y, y); } } int lca(int u, int v) { while (top[u] != top[v]) { if (dep[top[u]] < dep[top[v]]) swap(u, v); u = fa[top[u]]; } if (dep[u] < dep[v]) swap(u, v); return v; } int main() { memset(h, -1, sizeof h); scanf("%d%d%d", &n, &m, &root); for (int i = 0; i < n - 1; i ++ ) { int a, b; scanf("%d%d", &a, &b); add(a, b); add(b, a); } dfs1(root, -1, 1); dfs2(root, root); while (m -- ) { int x, y; scanf("%d%d", &x, &y); printf("%d\n", lca(x, y)); } return 0; }
题目:
给定一棵树,树中包含 nn 个节点(编号 1∼n),其中第 ii 个节点的权值为 ai。
初始时,1 号节点为树的根节点。
现在要对该树进行 m 次操作,操作分为以下 4种类型:
1 u v k
,修改路径上节点权值,将节点 u 和节点 v、v 之间路径上的所有节点(包括这两个节点)的权值增加 kk。2 u k
,修改子树上节点权值,将以节点 u 为根的子树上的所有节点的权值增加 k。3 u v
,询问路径,询问节点 u 和节点 v 之间路径上的所有节点(包括这两个节点)的权值和。4 u
,询问子树,询问以节点 u 为根的子树上的所有节点的权值和。输入格式
第一行包含一个整数 n,表示节点个数。
第二行包含 n 个整数,其中第 i 个整数表示 ai。
接下来 n− 行,每行包含两个整数 x,y,表示节点 x 和节点 y 之间存在一条边。
再一行包含一个整数 m,表示操作次数。
接下来 m 行,每行包含一个操作,格式如题目所述。
输出格式
对于每个操作 3 和操作 4,输出一行一个整数表示答案。
数据范围
1≤n,m≤105,
0≤ai,k≤105,
1≤u,v,x,y≤n输入样例:
5 1 3 7 4 5 1 3 1 4 1 5 2 3 5 1 3 4 3 3 5 4 1 3 5 10 2 3 5 4 1
输出样例:
16 69
分析:
树上任意两点之间查询和修改,对整棵子树进行查询和修改
树链+线段树
树链可以将一棵树转化成序列,使得树中任意一条路径可以分割成不超过logn段连续的序列。
如果是对一个子树求改和查询的话,就是对子树根节点维护,因为DFS在搜索的时候,就是一颗一颗子树搜索的,所以,一棵子树内的序列好是连续的
然后,问题就转化为了对连续区间的查询和修改,用线段树维护
代码:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 100010, M = N << 1; int n, m; int w[N], h[N], e[M], ne[M], idx; int id[N], nw[N], tot; int dep[N], sz[N], top[N], fa[N], son[N]; struct Node{ int l, r; ll add, sum; }tr[N << 2]; void add(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx ++; } void dfs1(int x, int father, int depth) { dep[x] = depth, fa[x] = father, sz[x] = 1; for (int i = h[x]; ~i; i = ne[i]) { int y = e[i]; if (y == father) continue; dfs1(y, x, depth + 1); sz[x] += sz[y]; if (sz[son[x]] < sz[y]) son[x] = y; } } void dfs2(int x, int t) { //重链的top一定是一个轻儿子 id[x] = ++ tot, nw[tot] = w[x], top[x] = t; if (!son[x]) return; dfs2(son[x], t); for (int i = h[x]; ~i; i = ne[i]) { int y = e[i]; if (y == fa[x] || y == son[x]) continue; dfs2(y, y); } } void pushup(int u) { tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum; } void pushdown(int u) { Node &c = tr[u], &a = tr[u << 1], &b = tr[u << 1 | 1]; if (c.add) { a.sum += c.add * (a.r - a.l + 1); a.add += c.add; b.sum += c.add * (b.r - b.l + 1); b.add += c.add; c.add = 0; } } void build(int u, int l, int r){ if (l == r) { tr[u] = {l, r, 0, nw[l]}; return; } tr[u] = {l, r}; int mid = l + r >> 1; build(u << 1, l, mid); build(u << 1 | 1, mid + 1, r); pushup(u); } void update(int u, int l, int r, int c) { if (tr[u].l >= l && tr[u].r <= r) { tr[u].sum += (tr[u].r - tr[u].l + 1) * c; tr[u].add += c; return; } pushdown(u); int mid = tr[u].l + tr[u].r >> 1; if (l <= mid) update(u << 1, l, r, c); if (r > mid) update(u << 1 | 1, l, r, c); pushup(u); } ll query(int u, int l, int r) { if (tr[u].l >= l && tr[u].r <= r) return tr[u].sum; pushdown(u); ll res = 0; int mid = tr[u].l + tr[u].r >> 1; if (l <= mid) res += query(u << 1, l, r); if (r > mid) res += query(u << 1 | 1, l, r); return res; } void update_path(int u, int v, int c) { //给路径从u到v都+c,显然分为在同一重链和不在同一重链上 //top不同,表明不在同一重链上 while (top[u] != top[v]) { //让u的重链的头结点在下面 if (dep[top[u]] < dep[top[v]]) swap(u, v); //更新u的从u位置一直到top[u],注意,DFS序前者大后者小 update(1, id[top[u]], id[u], c); u = fa[top[u]]; } //找到u和v更新到在同一条重链上 if (dep[u] < dep[v]) swap(u, v); update(1, id[v], id[u], c); } ll query_path(int u, int v) { ll res = 0; while (top[u] != top[v]) { if (dep[top[u]] < dep[top[v]]) swap(u, v); res += query(1, id[top[u]], id[u]); u = fa[top[u]]; } if (dep[u] < dep[v]) swap(u, v); res += query(1, id[v], id[u]); return res; } void update_tree(int u, int c) { //因为DFS是一棵子树搜完在搜其他子树,所以,一棵子树内的所有节点的DFS序是一段连续的区间 update(1, id[u], id[u] + sz[u] - 1, c); } ll query_tree(int u) { return query(1, id[u], id[u] + sz[u] - 1); } int main() { scanf("%d", &n); for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]); memset(h, -1, sizeof h); for (int i = 0; i < n - 1; i ++ ) { int a, b; scanf("%d%d", &a, &b); add(a, b); add(b, a); } dfs1(1, -1, 1); dfs2(1, 1); build(1, 1, n); scanf("%d", &m); while (m -- ) { int t, u, v, k; scanf("%d%d", &t, &u); if (t == 1) { scanf("%d%d", &v, &k); update_path(u, v, k); } else if (t == 2) { scanf("%d", &k); update_tree(u, k); } else if (t == 3) { scanf("%d", &v); printf("%lld\n", query_path(u, v)); } else { printf("%lld\n", query_tree(u)); } } return 0; }
P2146 [NOI2015] 软件包管理器
Linux 用户和 OSX 用户一定对软件包管理器不会陌生。
通过软件包管理器,你可以通过一行命令安装某一个软件包,然后软件包管理器会帮助你从软件源下载软件包,同时自动解决所有的依赖(即下载安装这个软件包的安装所依赖的其它软件包),完成所有的配置。
Debian/Ubuntu 使用的 apt-get,Fedora/CentOS 使用的 yum,以及 OSX 下可用的 homebrew 都是优秀的软件包管理器。
你决定设计你自己的软件包管理器。
不可避免地,你要解决软件包之间的依赖问题。
如果软件包 A 依赖软件包 B,那么安装软件包 A 以前,必须先安装软件包 B。
同时,如果想要卸载软件包 B,则必须卸载软件包 A。
现在你已经获得了所有的软件包之间的依赖关系。
而且,由于你之前的工作,除0 号软件包以外,在你的管理器当中的软件包都会依赖一个且仅一个软件包,而 0 号软件包不依赖任何一个软件包。
依赖关系不存在环(若有 m(m≥2) 个软件包 A1,A2,A3,…,Am,其中 A1 依赖 A2,A2 依赖 A3,A3 依赖 A4,……,Am−1 依赖 Am,而 Am 依赖 A1,则称这 m 个软件包的依赖关系构成环),当然也不会有一个软件包依赖自己。
现在你要为你的软件包管理器写一个依赖解决程序。
根据反馈,用户希望在安装和卸载某个软件包时,快速地知道这个操作实际上会改变多少个软件包的安装状态(即安装操作会安装多少个未安装的软件包,或卸载操作会卸载多少个已安装的软件包),你的任务就是实现这个部分。
注意,安装一个已安装的软件包,或卸载一个未安装的软件包,都不会改变任何软件包的安装状态,即在此情况下,改变安装状态的软件包数为 00。
输入格式
输入文件的第 1 行包含 1 个正整数 n,表示软件包的总数。软件包从 0 开始编号。
随后一行包含 n−1 个整数,相邻整数之间用单个空格隔开,分别表示 1,2,3,…,n−2,n−1 号软件包依赖的软件包的编号。
接下来一行包含 1 个正整数 q,表示询问的总数。
之后 q 行,每行 1 个询问。询问分为两种:
install x
:表示安装软件包 xxuninstall x
:表示卸载软件包 xx你需要维护每个软件包的安装状态,一开始所有的软件包都处于未安装状态。
对于每个操作,你需要输出这步操作会改变多少个软件包的安装状态,随后应用这个操作(即改变你维护的安装状态)。
输出格式
输出文件包括 qq 行。
输出文件的第 ii 行输出 11 个整数,为第 ii 步操作中改变安装状态的软件包数。
数据范围
输入样例1:
7 0 0 0 1 1 5 5 install 5 install 6 uninstall 1 install 4 uninstall 0
输出样例1:
3 1 3 2 3
输入样例2:
10 0 1 2 1 3 0 0 3 2 10 install 0 install 3 uninstall 2 install 7 install 5 install 9 uninstall 9 install 4 install 1 install 9
输出样例2
1 3 2 1 3 1 1 1 0 1
分析:
两种操作,
1:将根节点到当前节点的权值,全部变成1
2:将当前子树的权值,全部变成0
代码:
#include <bits/stdc++.h> using namespace std; const int N = 100005; int n, m; int h[N], e[N], ne[N], idx; int fa[N], son[N], sz[N], dep[N]; int id[N], top[N], tot; struct Node{ int l, r; int flag, sum; }tr[N << 2]; void add(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx ++; } void dfs1(int x, int depth) { dep[x] = depth, sz[x] = 1; for (int i = h[x]; ~i; i = ne[i]) { int y = e[i]; dfs1(y, depth + 1); sz[x] += sz[y]; if (sz[son[x]] < sz[y]) son[x] = y; } } void dfs2(int x, int t) { id[x] = ++ tot, top[x] = t; if (!son[x]) return; dfs2(son[x], t); for (int i = h[x]; ~i; i = ne[i]) { int y = e[i]; if (y == son[x]) continue; dfs2(y, y); } } void pushup(int u) { tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum; } void pushdown(int u) { Node &c = tr[u], &a = tr[u << 1], &b = tr[u << 1 | 1]; if (c.flag != -1) { a.sum = c.flag * (a.r - a.l + 1); b.sum = c.flag * (b.r - b.l + 1); a.flag = b.flag = c.flag; c.flag = -1; } } void build(int u, int l, int r) { if (l == r) { tr[u] = {l,r, -1, 0}; return; } tr[u] = {l, r}; int mid = l + r >> 1; build(u << 1, l, mid); build(u << 1 | 1, mid + 1, r); pushup(u); } void update(int u, int l, int r, int c) { if (tr[u].l >= l && tr[u].r <= r) { tr[u].sum = c * (tr[u].r - tr[u].l + 1); tr[u].flag = c; return; } pushdown(u); int mid = tr[u].l + tr[u].r >> 1; if (l <= mid) update(u << 1, l, r, c); if (r > mid) update(u << 1 | 1, l, r, c); pushup(u); } int query(int u, int l, int r) { if (tr[u].l >= l && tr[u].r <= r) return tr[u].sum; pushdown(u); int res = 0; int mid = tr[u].l + tr[u].l >> 1; if (l <= mid) res += query(u << 1, l, r); if (r > mid) res += query(u << 1 | 1, l, r); return res; } void update_path(int u, int v, int c) { while (top[u] != top[v]) { if (dep[top[u]] < dep[top[v]]) swap(u, v); update(1, id[top[u]], id[u], c); u = fa[top[u]]; } if (dep[u] < dep[v]) swap(u, v); update(1, id[v], id[u], c); } void update_tree(int u, int c) { update(1, id[u], id[u] + sz[u] - 1, c); } int main() { memset(h, -1, sizeof h); cin >> n; for (int i = 2; i <= n; i ++ ) { int x; scanf("%d", &x); x ++; add(x, i); fa[i] = x; } dfs1(1, 1); dfs2(1, 1); build(1, 1, n); cin >> m; char op[20]; int x; while (m -- ) { scanf("%s%d", op, &x); x ++; if (!strcmp(op, "install")) { int sum = query(1, 1, n); update_path(1, x, 1); int ans = query(1, 1, n) - sum; printf("%d\n", ans); } else { int sum = query(1, 1, n); update_tree(x, 0); int ans = sum - query(1, 1, n); printf("%d\n", ans); } } return 0; }
P2486 [SDOI2011]染色
题目:
题目描述
给定一棵 n 个节点的无根树,共有mm 个操作,操作分为两种:
- 将节点 a 到节点 b 的路径上的所有点(包括 a 和 b)都染成颜色 c。
- 询问节点 a 到节点 b 的路径上的颜色段数量。
颜色段的定义是极长的连续相同颜色被认为是一段。例如
112221
由三段组成:11
、222
、1
。输入格式
输入的第一行是用空格隔开的两个整数,分别代表树的节点个数 n 和操作个数 m。
第二行有 n 个用空格隔开的整数,第 i 个整数wi 代表结点 i 的初始颜色。
第 3 到第 (n+1) 行,每行两个用空格隔开的整数 u,v,代表树上存在一条连结节点 u 和节点 v 的边。
第 (n+2) 到第 (n+m+1) 行,每行描述一个操作,其格式为:
每行首先有一个字符 op,代表本次操作的类型。
- 若 op 为
C
,则代表本次操作是一次染色操作,在一个空格后有三个用空格隔开的整数 a,b,c,代表将 a 到 b 的路径上所有点都染成颜色 cc。- 若 op 为
Q
,则代表本次操作是一次查询操作,在一个空格后有两个用空格隔开的整数 a,b,表示查询 a 到 b 路径上的颜色段数量。输出格式
对于每次查询操作,输出一行一个整数代表答案。
输入输出样例
输入 #16 5 2 2 1 2 1 1 1 2 1 3 2 4 2 5 2 6 Q 3 5 C 2 1 1 Q 3 5 C 5 1 2 Q 3 5输出 #13 1 2说明/提示
数据规模与约定
对于 100% 的数据,1≤n,m≤105,1≤wi,c≤109,1≤a,b,u,v≤n,op 一定为
C
或Q
,保证给出的图是一棵树。除原数据外,还存在一组不计分的 hack 数据。
分析:
树剖后,用线段树维护左端点l,右端点r,左端点颜色lc,右端点rc,区间更新颜色的标记tag,区间颜色的段树num。
现在对于维护线段树需要注意的是,在合并左子树和右子树的时候,左子树的右端点如果和右子树的左端点的颜色相同,那么,合并后的二叉线段树的num需要减一。
下面考虑树剖的问题, 如果当前树剖到的链与上一次的链在相交的边缘颜色可能相等,如果相等,答案需要减一。所以,统计答案的时候,需要记录一下上一次剖到的链的左端点的颜色(左端点指的是索引小的那头,也就是子树的树根位置),与当前树剖的右端点的颜色,比较两个颜色,如果相同则答案减一。
注意:由于u和v两个位置向上走,那么要记录ans1,ans2两个变量来存储上一次的左端点颜色。当top[u] = top[v]的时候, 已经在同一个重链上了,两边端点的颜色都要考虑与对应的ans比较颜色,相同答案要相应减一。
代码:
#include <bits/stdc++.h> using namespace std; const int N = 100005; int n, m; int w[N],h[N], e[N << 1], ne[N << 1], idx; int fa[N], son[N], sz[N], dep[N]; int id[N], nw[N], top[N], tot; void add_edges(int a,int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx ++; } void dfs1(int x, int father, int depth) { dep[x] = depth, fa[x] = father, sz[x] = 1; for (int i = h[x]; ~i; i = ne[i]) { int y = e[i]; if (y == father) continue; dfs1(y, x, depth + 1); sz[x] += sz[y]; if (sz[son[x]] < sz[y]) son[x] = y; } } void dfs2(int x, int t) { id[x] = ++ tot, nw[tot] = w[x], top[x] = t; if (!son[x]) return; dfs2(son[x], t); for (int i = h[x]; ~i; i = ne[i]) { int y = e[i]; if (y == fa[x] || y == son[x]) continue; dfs2(y, y); } } struct Node{ int l, r; int num, tag, lc, rc; }tr[N << 2]; int Lc, Rc; void pushup(int u) { Node &c = tr[u], &a = tr[u << 1], &b = tr[u << 1 | 1]; c.lc = a.lc, c.rc = b.rc; c.num = a.num + b.num; if (a.rc == b.lc) c.num --; } void pushdown(int u) { Node &c = tr[u], &a = tr[u << 1], &b = tr[u << 1 | 1]; if (c.tag) { a.num = b.num = c.num; a.lc = a.rc = b.lc = b.rc = c.lc; a.tag = b.tag = c.tag; c.tag = 0; } } void build(int u, int l, int r) { tr[u] = {l, r, 0, 0, 0, 0}; if (l == r) return; int mid = l + r >> 1; build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r); } void update(int u, int l, int r, int c) { if (tr[u].l == l && tr[u].r == r) { tr[u].tag = tr[u].num = 1; tr[u].lc = tr[u].rc = c; return; } pushdown(u); int mid = tr[u].l + tr[u].r >> 1; if (r <= mid) update(u << 1, l, r, c); else if (l > mid) update(u << 1 | 1, l, r, c); else update(u << 1, l, mid, c), update(u << 1 | 1, mid + 1, r, c); pushup(u); } int query(int u, int l, int r, int L, int R) { if (tr[u].l == L) Lc = tr[u].lc; if (tr[u].r == R) Rc = tr[u].rc; if (tr[u].l == l && tr[u].r == r) return tr[u].num; pushdown(u); int mid = tr[u].l + tr[u].r >> 1; if (r <= mid) return query(u << 1, l, r, L, R); else if (l > mid) return query(u << 1 | 1, l, r, L, R); else { int ans = query(u << 1, l, mid, L, R) + query(u << 1 | 1, mid + 1, r, L, R); if (tr[u << 1].rc == tr[u << 1 | 1].lc) ans --; return ans; } pushup(u); } void update_path(int u, int v, int c) { while (top[u] != top[v]) { if (dep[top[u]] < dep[top[v]]) swap(u, v); update(1, id[top[u]], id[u], c); u = fa[top[u]]; } if (dep[u] < dep[v]) swap(u, v); update(1, id[v], id[u], c); } int query_path(int u, int v) { int ans1 = -1, ans2 = -1; int ans = 0; while (top[u] != top[v]) { if (dep[top[u]] < dep[top[v]]) swap(u, v), swap(ans1, ans2); ans += query(1, id[top[u]], id[u], id[top[u]], id[u]); //在swap后,ans1表示这颗树链下方的节点 if (Rc == ans1) ans --; ans1 = Lc, u = fa[top[u]]; } if (dep[u] < dep[v]) swap(u, v), swap(ans1, ans2); ans += query(1, id[v], id[u], id[v], id[u]); if (Rc == ans1) ans --; if (Lc == ans2) ans --; return ans; } int main() { memset(h, -1, sizeof h); scanf("%d%d", &n, &m); for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]); for (int i = 0; i < n - 1; i ++ ) { int a, b; scanf("%d%d", &a, &b); add_edges(a, b); add_edges(b, a); } dfs1(1, -1, 1); dfs2(1, 1); // printf("-------------\n"); build(1, 1, n); for (int i = 1; i <= n; i ++ ) update(1, id[i], id[i], w[i]); char op[2]; int l, r, c; while (m -- ) { scanf("%s%d%d", op, &l, &r); if (*op == 'C') { scanf("%d", &c); update_path(l, r, c); } else { printf("%d\n", query_path(l, r)); } } }
P2590 [ZJOI2008]树的统计
题目:
题目描述
一棵树上有 n 个节点,编号分别为 1 到 n,每个节点都有一个权值 w。
我们将以下面的形式来要求你对这棵树完成一些操作:
I.
CHANGE u t
: 把结点 u 的权值改为 t。II.
QMAX u v
: 询问从点 u 到点 v 的路径上的节点的最大权值。III.
QSUM u v
: 询问从点 u 到点 v 的路径上的节点的权值和。注意:从点 u 到点 v 的路径上的节点包括 u 和 v 本身。
输入格式
输入文件的第一行为一个整数 n,表示节点的个数。
接下来 n−1 行,每行 2 个整数 a 和 b,表示节点 a 和节点 b 之间有一条边相连。
接下来一行 n 个整数,第 i 个整数 wi 表示节点 ii 的权值。
接下来 1 行,为一个整数 q,表示操作的总数。
接下来 q 行,每行一个操作,以
CHANGE u t
或者QMAX u v
或者QSUM u v
的形式给出。输出格式
对于每个
QMAX
或者QSUM
的操作,每行输出一个整数表示要求输出的结果。输入输出样例
输入 #14 1 2 2 3 4 1 4 2 1 3 12 QMAX 3 4 QMAX 3 3 QMAX 3 2 QMAX 2 3 QSUM 3 4 QSUM 2 1 CHANGE 1 5 QMAX 3 4 CHANGE 3 6 QMAX 3 4 QMAX 2 4 QSUM 3 4输出 #14 1 2 2 10 6 5 6 5 16说明/提示
对于 100% 的数据,保证 1≤n≤3×104,0≤q≤2×105。
中途操作中保证每个节点的权值 w 在 −3×104 到 3×104 之间。
分析:
这里的sum和max性质不同,还是写两个函数来求解吧
代码:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 100005; int n, m; int w[N], h[N], e[N], ne[N << 1], idx; int fa[N], son[N], sz[N], dep[N]; int id[N], nw[N], top[N], tot; void add_edges(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx ++; } void dfs1(int x, int father, int depth) { dep[x] = depth, fa[x] = father, sz[x] = 1; for (int i = h[x]; ~i; i = ne[i]) { int y = e[i]; if (y == father) continue; dfs1(y, x, depth + 1); sz[x] += sz[y]; if (sz[son[x]] < sz[y]) son[x] = y; } } void dfs2(int x, int t) { id[x] = ++ tot, nw[tot] = w[x], top[x] = t; if (!son[x]) return; dfs2(son[x], t); for (int i = h[x]; ~i; i = ne[i]) { int y = e[i]; if (y == fa[x] || y == son[x]) continue; dfs2(y, y); } } struct Node{ int l, r; ll mx, sum; }tr[N << 2]; void pushup(Node &c, Node a, Node b) { c.mx = max(a.mx, b.mx); c.sum = a.sum + b.sum; } void pushup(int u) { pushup(tr[u], tr[u << 1], tr[u << 1 | 1]); } void build(int u, int l, int r) { if (l == r) { tr[u] = {l, l, nw[l], nw[l]}; return; } tr[u] = {l, r}; int mid = l + r >> 1; build(u << 1, l, mid); build(u << 1 | 1, mid + 1, r); pushup(u); } void update(int u, int x, int c) { if (tr[u].l == x && tr[u].r == x) { tr[u] = {x, x, c, c}; return; } int mid = tr[u].l + tr[u].r >> 1; if (x <= mid) update(u << 1, x, c); else update(u << 1 | 1, x, c); pushup(u); } int qmax(int u, int l, int r) { if (tr[u].l == l && tr[u].r == r) return tr[u].mx; int mx = -2e9; int mid = tr[u].l + tr[u].r >> 1; if (r <= mid) mx = max(mx, qmax(u << 1, l, r)); else if (l > mid) mx = max(mx, qmax(u << 1 | 1, l, r)); else { mx = max(mx, max(qmax(u << 1, l, mid), qmax(u << 1 | 1, mid + 1, r))); } return mx; } int qsum(int u, int l, int r) { if (tr[u].l == l && tr[u].r == r) return tr[u].sum; int ans = 0; int mid = tr[u].l + tr[u].r >> 1; if (r <= mid) ans += qsum(u << 1, l, r); else if (l > mid) ans +=qsum(u << 1 | 1, l, r); else { ans += qsum(u << 1, l, mid) + qsum(u << 1 | 1, mid + 1, r); } return ans; } void update_path(int x, int c) { update(1, id[x], c); } int Qmax(int u, int v) { int mx = -2e9; while (top[u] != top[v]) { if (dep[top[u]] < dep[top[v]]) swap(u, v); mx = max(mx, qmax(1, id[top[u]], id[u])); u = fa[top[u]]; } if (dep[u] < dep[v]) swap(u, v); mx = max(mx, qmax(1, id[v], id[u])); return mx; } int Qsum(int u, int v) { int ans = 0; while (top[u] != top[v]) { if (dep[top[u]] < dep[top[v]]) swap(u, v); ans += qsum(1, id[top[u]], id[u]); u = fa[top[u]]; } if (dep[u] < dep[v]) swap(u, v); ans += qsum(1, id[v], id[u]); return ans; } int main() { memset(h, -1, sizeof h); cin >> n; for (int i = 1; i < n; i ++ ) { int a, b; scanf("%d%d", &a, &b); add_edges(a, b); add_edges(b, a); } for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]); dfs1(1, -1, 1); dfs2(1, 1); build(1, 1, n); cin >> m; char op[20]; while (m -- ) { scanf("%s", op); if (!strcmp(op, "CHANGE")) { int x, c; scanf("%d%d", &x, &c); update_path(x, c); } else if (!strcmp(op, "QMAX")) { int u, v; scanf("%d%d", &u, &v); printf("%d\n", Qmax(u, v)); } else { int u, v; scanf("%d%d", &u, &v); printf("%d\n", Qsum(u, v)); } // cout << "----------" << endl; } return 0; }
P3178 [HAOI2015]树上操作
题目:
题目描述
有一棵点数为 N 的树,以点 1 为根,且树点有边权。然后有 M 个操作,分为三种:
- 操作 1 :把某个节点 x 的点权增加 a 。
- 操作 2 :把某个节点 x 为根的子树中所有点的点权都增加 a 。
- 操作 3 :询问某个节点 x 到根的路径中所有点的点权和。
输入格式
第一行包含两个整数 N, M 。表示点数和操作数。
接下来一行 N 个整数,表示树中节点的初始权值。
接下来 N-1 行每行两个正整数 from, to , 表示该树中存在一条边 (from, to) 。
再接下来 M 行,每行分别表示一次操作。其中第一个数表示该操作的种类( 1-3 ) ,之后接这个操作的参数( x 或者 x a ) 。输出格式
对于每个询问操作,输出该询问的答案。答案之间用换行隔开。
输入输出样例
输入 #15 5 1 2 3 4 5 1 2 1 4 2 3 2 5 3 3 1 2 1 3 5 2 1 2 3 3输出 #16 9 13说明/提示
对于 100% 的数据, N,M<=100000 ,且所有输入数据的绝对值都不会超过 10^6 。
分析:
这题也很好写啊,真心不知道为啥全部改成long long就可以过,而部分为long long就WA
代码:
#include <bits/stdc++.h> using namespace std; // typedef long long ll; #define int long long const int N = 100005; int n, m; int w[N], h[N], e[N << 1], ne[N << 1], idx; int fa[N], son[N], sz[N], dep[N]; int id[N], nw[N], top[N], tot; void add_edges(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx ++; } void dfs1(int x, int father, int depth) { dep[x] = depth, fa[x] = father, sz[x] = 1; for (int i = h[x]; ~i; i = ne[i]) { int y = e[i]; if (y == father) continue; dfs1(y, x, depth + 1); sz[x] += sz[y]; if (sz[son[x]] < sz[y]) son[x] = y; } } void dfs2(int x, int t) { id[x] = ++ tot, nw[tot] = w[x], top[x] = t; if (!son[x]) return; dfs2(son[x], t); for (int i = h[x]; ~i; i = ne[i]) { int y = e[i]; if (y == fa[x] || y == son[x]) continue; dfs2(y, y); } } struct Node{ int l, r; int tag, sum; }tr[N << 2]; void pushup(int u) { tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum; } void pushdown(int u) { Node &c = tr[u], &a = tr[u << 1], &b = tr[u << 1 | 1]; if (c.tag) { a.sum += c.tag * (a.r - a.l + 1); b.sum += c.tag * (b.r - b.l + 1); a.tag += c.tag; b.tag += c.tag; c.tag = 0; } } void build(int u, int l, int r) { if (l == r) { tr[u] = {l, l, 0, nw[l]}; return; } tr[u] = {l, r}; int mid = l + r >> 1; build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r); pushup(u); } void update(int u, int l, int r, int c) { if (tr[u].l >= l && tr[u].r <= r) { tr[u].sum += (tr[u].r - tr[u].l + 1) * c; tr[u].tag += c; return; } pushdown(u); int mid = tr[u].l + tr[u].r >> 1; if (l <= mid) update(u << 1, l, r, c); if (r > mid) update(u << 1 | 1, l, r, c); pushup(u); } int query(int u, int l, int r) { if (tr[u].l >= l && tr[u].r <= r) return tr[u].sum; pushdown(u); int res = 0; int mid = tr[u].l + tr[u].r >> 1; if (l <= mid) res += query(u << 1, l, r); if (r > mid) res += query(u << 1 | 1, l, r); return res; } void update_path(int u, int c, int tag) { if (tag == 2) { update(1, id[u], id[u] + sz[u] - 1, c); } else { update(1, id[u], id[u], c); } } int query_path(int u, int v) { int ans = 0; while (top[u] != top[v]) { // if (dep[top[u]] < dep[top[v]]) swap(u, v); ans += query(1, id[top[u]], id[u]); u = fa[top[u]]; } // if (dep[u] < dep[v]) swap(u, v); ans += query(1, id[v], id[u]); return ans; } signed main() { memset(h, -1, sizeof h); cin >> n >> m; for (int i = 1; i <= n; i ++ ) scanf("%lld", &w[i]); for (int i = 1; i < n; i ++ ) { int a, b; scanf("%lld%lld", &a, &b); add_edges(a, b); add_edges(b, a); } dfs1(1, 1, 1); dfs2(1, 1); build(1, 1, n); while (m -- ) { int p; scanf("%lld", &p); if (p == 1) { // printf("operation 1 :\n"); // for (int i = 1; i <= n * 2 - 1; i ++ ) cout << tr[i].sum << " "; cout << endl; int x, c; scanf("%lld%lld", &x, &c); update_path(x, c, 1); // for (int i = 1; i <= n * 2 - 1; i ++ ) cout << tr[i].sum << " "; cout << endl; } else if (p == 2) { // printf("operation 2 :\n"); // for (int i = 1; i <= n * 2 - 1; i ++ ) cout << tr[i].sum << " "; cout << endl; int x, c; scanf("%lld%lld", &x, &c); update_path(x, c, 2); // for (int i = 1; i <= n * 2 - 1; i ++ ) cout << tr[i].sum << " "; cout << endl; } else { int x; scanf("%lld", &x); printf("%lld\n", query_path(x, 1)); } } return 0; }