树链剖分
一树上有 n 个节点,编号分别为 1 到 n,每个节点都有一个权值 w。我们将以下面的形式来要求你对这棵树完成一些操作:
1.CHANGE u t :把节点 u 权值改为 t;
2.QMAX u v :询问点 u 到点 v 路径上的节点的最大权值;
3.QSUM u v :询问点 u 到点 v 路径上的节点的权值和。
注意:从点 u 到点 v 路径上的节点包括 u 和 v 本身。
输入格式
第一行为一个数 n,表示节点个数;
接下来 n−1 行,每行两个整数 a,b,表示节点 a 与节点 b 之间有一条边相连;
接下来一行 n 个整数,第 i 个整数 wi 表示节点 i 的权值;
接下来一行,为一个整数 q ,表示操作总数;
接下来 q 行,每行一个操作,以 CHANGE u t 或 QMAX u v 或 QSUM u v的形式给出。
输出格式
对于每个 QMAX 或 QSUM 的操作,每行输出一个整数表示要求的结果。
样例
Input Output
4
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
Output
4
1
2
2
10
6
5
6
5
16
数据范围与提示
对于 100% 的数据,有 1≤n≤3×104,0≤q≤2×105。中途操作中保证每个节点的权值 w 在 −30000 至 30000 之间。
原题来自:ZJOI 2008
题目链接
算是个树链剖分裸题吧
然后贴代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 30010, M = N * 2, INF = 0x3f3f3f3f;
struct Node{
int l, r;
int d, sum;
}tr[N * 4];
int h[N], e[M], ne[M], idx;
void add(int a, int b)//添加边a到b
{
e[idx] = b;//终点指向b
ne[idx] = h[a];
h[a] = idx ++;//idx是边的总数
}
int n;
int w[N];
int f[N], dep[N], sz[N], son[N]; // 父节点,深度,子树大小,重儿子
void dfs1(int u, int fa) // 当前节点,父节点
{
f[u] = fa;
dep[u] = dep[fa] + 1;
sz[u] = 1;
for(int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if(j == fa) continue;//因为是无向边所以如果遇到父亲就continue
dfs1(j, u);
sz[u] += sz[j];
if(sz[j] > sz[son[u]]) son[u] = j;
}
}
int top[N], id[N], rk[N], tot; // 链顶,原编号对应的新编号,新编号对应的原编号
void dfs2(int u, int t) // 当前节点,链顶
{
top[u] = t;
id[u] = ++ tot;//建立dfs序 tot是按照dfs序的边的总数,这一步是把DFS序记录在原树的节点上
rk[tot] = u;// dfs序编号对应的原树的节点号
if(son[u]) dfs2(son[u], t);//如果存在中儿子那么先遍历重儿子给dfs序
for(int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if(j == f[u] or j == son[u]) continue;//重儿子已经被遍历过了所以父亲和重儿子直接过
dfs2(j, j);//注意重连的头是由轻儿子开始的
}
}
void build(int u, int l, int r)//建立以DFS序为区间的线段树
{
tr[u] = {l, r};
if(l == r)
{
tr[u].d = tr[u].sum = w[rk[l]];
return;
}
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
tr[u].d = max(tr[2*u].d, tr[2*u+1].d);//更新最大值
tr[u].sum = tr[2*u].sum + tr[2*u+1].sum;//更新和
}
void modify(int u, int x, int c)//更改权值,u是当前结点,由1开始,
//x是要更改节点对应的dfs序的值,c是要修改的值 这样就可以在线段树里面修正了
{
if(tr[u].l == x and tr[u].r == x)
{
tr[u].d = tr[u].sum = c;
return;
}
int mid = tr[u].l + tr[u].r >> 1;
if(x <= mid) modify(u << 1, x, c);
else modify(u << 1 | 1, x, c);
tr[u].d = max(tr[2*u].d, tr[2*u+1].d);//更新最大值
tr[u].sum = tr[2*u].sum + tr[2*u+1].sum;//更新和
}
int qmax(int u, int l, int r)//用线段树找所在区间最大值,由性质可知一条重连的dfs序是连续的
{
if(tr[u].l >= l and tr[u].r <= r) return tr[u].d;
int res = -INF;
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) res = max(res, qmax(u << 1, l, r));
if(r > mid) res = max(res, qmax(u << 1 | 1, l, r));
return res;
}
int querymax(int a, int b)//询问原树上点a到b上最大值
{
int res = -INF;
while(top[a] != top[b])//根节点不一样,说明不在同一重链上
{
if(dep[top[a]] < dep[top[b]]) swap(a, b);//swap交换ab,是令top[a]比top[b]深
res = max(res, qmax(1, id[top[a]], id[a]));//用线段树寻找根节点深度大的重链的max
a = f[top[a]];//top[a]是根节点,是个轻儿子,f[top[a]]是在另一条重重上的一个重儿子
}
if(dep[a] > dep[b]) swap(a, b); // 令a在b上面
res = max(res, qmax(1, id[a], id[b]));//这时候a,b已经在一条重连上了,更新一下就行
return res;
}
int qsum(int u, int l, int r)//和上面的同理
{
if(tr[u].l >= l and tr[u].r <= r) return tr[u].sum;
int res = 0;
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) res += qsum(u << 1, l, r);
if(r > mid) res += qsum(u << 1 | 1, l, r);
return res;
}
int querysum(int a, int b)//同理
{
int res = 0;
while(top[a] != top[b])
{
if(dep[top[a]] < dep[top[b]]) swap(a, b); // 令top[a]比top[b]深
res += qsum(1, id[top[a]], id[a]);
a = f[top[a]];
}
if(dep[a] > dep[b]) swap(a, b); // 令a在b上面
res += qsum(1, id[a], id[b]);
return res;
}
int main()
{
memset(h, -1, sizeof h);
scanf("%d", &n);
for(int i = 1; i < n; i ++)
{
int a, b;
scanf("%d%d", &a, &b);
add(a, b);//无向边
add(b, a);
}
for(int i = 1; i <= n; i ++) scanf("%d", &w[i]);
dfs1(1, 0);
dfs2(1, 1);
build(1, 1, n);
int m;
scanf("%d", &m);
while(m --)
{
char op[10];
int a, b;
scanf("%s%d%d", op, &a, &b);
if(op[0] == 'C') modify(1, id[a], b);
else if(op[1] == 'M') printf("%d\n", querymax(a, b));
else printf("%d\n", querysum(a, b));
}
return 0;
}
2021.7.17