文章目录
专题三十一 树链剖分
HDU 3966
题意: 题意:一颗树上,每个点有权值,定义三种操作:
1)I操作表示从a到b节点之间的节点都加上一个值
2)D操作表示从a到b节点之间的节点的都减去一个权值
3)Q操作询问a节点当前的值。
题解: 树剖+线段树裸题。
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 50000 + 10;
int tot, num;
int n, m, r;
inline int read() {
int s = 0, w = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') w = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
return s * w;
}
int w[N], a[N], dat[N * 4],
lazy[N *
4]; // w[i]=j表示时间戳为i的点的值为j,a[]输入每个节点的值,dat线段树每个点权值,lazy线段树每个点的懒标记
int h[N], e[N * 2], ne[N * 2], idx; // 邻接表数组
int dep[N], dfn[N], wson[N], sz[N], top[N],
fa[N], Q; // dep深度 dfn搜索序 wson重儿子 size子树大小 top链头 fa父节点
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++; }
// 得到sz, fa, dep, wson数组
void dfs1(int u) {
dep[u] = dep[fa[u]] + 1;
sz[u] = 1;
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (j == fa[u]) continue;
fa[j] = u;
dfs1(j);
sz[u] += sz[j];
if (sz[j] > sz[wson[u]])
wson[u] =
j; // 这里要注意根节点不能设为0,否则根节点的最重链无法更新,始终为0
}
}
// 得到dfn, top数组
void dfs2(int u, int nowtop) {
dfn[u] = ++num;
w[num] = a[u];
//以搜索序重排权值
top[u] = nowtop;
if (wson[u]) dfs2(wson[u], nowtop); // 先搜索重儿子
for (int i = h[u]; ~i; i = ne[i]) {
// 然后搜索轻儿子
int y = e[i];
if (y == fa[u] || y == wson[u]) continue;
dfs2(y, y);
}
}
void pushup(int rt) {
dat[rt] = dat[rt << 1] + dat[rt << 1 | 1];
}
// 建线段树,rt为根,l为rt点管辖的左边界, r为rt点管辖的有边界
void build(int rt, int l, int r) {
if (l == r) {
dat[rt] = w[l];
return;
}
int mid = (l + r) >> 1;
build(rt << 1, l, mid);
build(rt << 1 | 1, mid + 1, r);
pushup(rt);
}
// 下传
void pushdown(int rt, int l, int r) {
if (lazy[rt]) {
int mid = (l + r) >> 1;
dat[rt << 1] += lazy[rt] * (mid - l + 1),
lazy[rt << 1] += lazy[rt];
dat[rt << 1 | 1] += lazy[rt] * (r - mid),
lazy[rt << 1 | 1] += lazy[rt];
lazy[rt] = 0;
}
}
// rt为根,l为rt点管辖的左边界, r为rt点管辖的有边界,
// l为需要修改的左区间,r为需要修改的右区间
void modify(int rt, int l, int r, int L, int R, int k) {
if (L <= l && r <= R) {
dat[rt] += k * (r - l + 1);
lazy[rt] += k;
return;
}
pushdown(rt, l, r);
int mid = (l + r) >> 1;
if (L <= mid) modify(rt << 1, l, mid, L, R, k);
if (mid < R) modify(rt << 1 | 1, mid + 1, r, L, R, k);
pushup(rt);
}
// rt为根,l为rt点管辖的左边界, r为rt点管辖的有边界,
// l为需要查询的左区间,r为查询的右区间
int query(int rt, int l, int r, int L, int R) {
if (L <= l && r <= R) {
return dat[rt];
}
pushdown(rt, l, r);
int mid = (l + r) >> 1;
int ans = 0;
if (L <= mid) ans += query(rt << 1, l, mid, L, R);
if (mid < R) ans += query(rt << 1 | 1, mid + 1, r, L, R);
return ans;
}
// 求树从 x 到 y 结点最短路径上所有节点的值之和
int path_query_Tree(int x, int y) {
//两点间的修改
int ans = 0;
while (top[x] != top[y]) {
// 把x点和y点整到一条重链上
if (dep[top[x]] < dep[top[y]]) swap(x, y); // 让x点为深的那个点
ans += query(1, 1, n, dfn[top[x]], dfn[x]);
x = fa[top[x]]; // x每次跳一条链
}
if (dep[x] > dep[y]) swap(x, y); // 让x成为深度更浅的那个点
ans += query(1, 1, n, dfn[x], dfn[y]);
return ans;
}
// 将树从 x到 y 结点最短路径上所有节点的值都加上 z
void path_modify_Tree(int x, int y, int k) {
//树上两点距离
while (top[x] != top[y]) {
// 把x点和y点整到一条重链上
if (dep[top[x]] < dep[top[y]])
swap(x, y); // 让x成为对应的头部深度更大的那个点
modify(1, 1, n, dfn[top[x]], dfn[x], k); // 累加x的所有子树和
x = fa[top[x]]; // x跳到原来x的头部的父节点
}
if (dep[x] > dep[y]) swap(x, y); // 让x成为深度更浅的那个点
modify(1, 1, n, dfn[x], dfn[y], k);
}
signed main() {
while(scanf("%lld%lld%lld", &n, &m, &Q) != EOF) {
for (int i = 1; i <= n; ++i) h[i] = -1, wson[i] = 0;
memset(lazy, 0, sizeof lazy);
idx = num = 0;
r = 1;
for (int i = 1; i <= n; i++) scanf("%lld", &a[i]); // 读入每个点的权值
// 读入边,建树
for (int i = 0, x, y; i < m; i++) {
x = read(), y = read();
add(x, y);
add(y, x);
}
// 两次dfs把树按照重链剖分
dfs1(r); // 得到sz, fa, dep, wson数组
dfs2(r, r); // 得到dfn, top数组
build(1, 1, n);
// m次询问
// cout << n << " " << m << " " << Q << endl;
char op[2];
for (int i = 1, x, y, z; i <= Q; i++) {
scanf("%s", op);
if (op[0] == 'I') {
scanf("%lld%lld%lld", &x, &y, &z);
path_modify_Tree(
x, y, z); // 将树从 x到 y 结点最短路径上所有节点的值都加上 z。
} else if (op[0] == 'Q') {
scanf("%lld", &x);
printf("%lld\n",
path_query_Tree(
x, x)); //求树从 x 到 y 结点最短路径上所有节点的值之和
} else if (op[0] == 'D') {
scanf("%lld%lld%lld", &x, &y, &z);
path_modify_Tree(
x, y, -z); // 将树从 x到 y 结点最短路径上所有节点的值都加上 z。
}
}
}
return 0;
}
POJ 2763 Housewife Wind
题意: 有一个人在一棵树的某个节点s,然后给出了树的每条边都有一个权值,有两种操作:
0 a 是问从节点s到a的路径权值和,然后a就成了s;
1 a b把第a条边权值变为b。 1 < = n < = 1 0 5 1<= n<=10^5 1<=n<=105
题解: 边权转换为点权,然后套上线段树。
代码:
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#define int long long
using namespace std;
const int N = 100001 + 10;
int tot, num;
int n, m, r, q, s;
int w[N], a[N], dat[N * 4], lazy[N * 4]; // w[i]=j表示时间戳为i的点的值为j,a[]输入每个节点的值,dat线段树每个点权值,lazy线段树每个点的懒标记
int h[N], e[N * 2], ne[N * 2], w2[N * 2], idx; // 邻接表数组
int dep[N], dfn[N], wson[N], sz[N], top[N], fa[N]; // dep深度 dfn搜索序 wson重儿子 size子树大小 top链头 fa父节点
void add(int a, int b, int c) {
e[idx] = b, w2[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
// 得到sz, fa, dep, wson数组
void dfs1(int u) {
dep[u] = dep[fa[u]]+1;
sz[u] = 1;
for(int i = h[u]; ~i; i = ne[i]) {
int j=e[i];
if(j == fa[u]) continue;
fa[j] = u;
dfs1(j);
sz[u] += sz[j];
if(sz[j] > sz[wson[u]]) wson[u] = j; // 这里要注意根节点不能设为0,否则根节点的最重链无法更新,始终为0
}
}
// 得到dfn, top数组
void dfs2(int u, int nowtop) {
dfn[u] = ++num;
//以搜索序重排权值
top[u] = nowtop;
if(wson[u]) dfs2(wson[u], nowtop); // 先搜索重儿子
for(int i = h[u]; ~i; i = ne[i]) {
// 然后搜索轻儿子
int y=e[i];
if(y ==fa[u]||y == wson[u]) continue;
dfs2(y, y);
}
}
void pushup(int rt) {
dat[rt] = dat[rt << 1] + dat[rt << 1 | 1];
}
// 建线段树,rt为根,l为rt点管辖的左边界, r为rt点管辖的有边界
void build(int rt, int l, int r) {
if(l==r) {
dat[rt]=w[l];
return ;
}
int mid=(l + r)>>1;
build(rt << 1, l, mid);
build(rt << 1 | 1, mid+1, r);
pushup(rt);
}
// 下传
void pushdown(int rt, int l, int r) {
if(lazy[rt]) {
int mid=(l + r)>>1;
dat[rt << 1] = lazy[rt]*(mid - l + 1), lazy[rt << 1] = lazy[rt