树链剖分 | 洛谷 P4114 Qtree1

前言

题目链接:洛谷 P4114 Qtree1

前置知识:树链剖分


题意

给定一棵树,有修改边权和查询两点之间边权最大值两种操作,对于每个查询输出结果。

解析

已经在前置博客里提到,树链剖分 可以将树上的任意一条路径划分成不超过 \(O(\log n)\) 条连续的链,保证划分出的每条链上的节点 DFS 序 连续。这大大方便了我们维护点权,但该如何维护边权呢?

这里就要介绍一种重要的思想——化边权为点权。

\(1\) 号点开始,遍历一遍整棵树,对于每条边,把边权赋在后遍历到的那个点,也就是 \(dep\) 更大的点上。这样可以保证,在跳祖先的过程中,必然能够不重不漏地计算到每个边权。若把边权赋在 \(dep\) 更大的点上则不然。最后就是简单的 树剖+线段树 维护最大值的过程。

查询操作不用多说,就是树剖的常规操作,不断跳祖先查询最大值即可。

修改操作就比较复杂了,要找到第 \(i\) 条边上 \(dep\) 较大的点(因为边权存在这个点上),然后使用线段树单点修改。

这里要注意,使用 链式前向星 存图会比 vector 邻接表方便更多,因为我们要找到输入的第 \(i\) 条边。

代码

// Problem: P4114 Qtree1
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P4114
// Memory Limit: 500 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

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

#define int long long
const int N = 100005;
int n, u, v, w, cnt, tot, a, b;
int head[N], top[N], dep[N], dfn[N], heavy[N], son[N], val[N], f[N];
string s;

struct edge
{
	int from, to, val, nxt;
}e[N << 1];

inline void add_edge(int u, int v, int w)
{
	e[++tot] = {u, v, w, head[u]}, head[u] = tot;
}

inline void dfs1(int u, int fa)
{
	dep[u] = dep[fa] + 1, f[u] = fa, son[u] = 1;
	for(int i = head[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if(v == fa) continue;
		dfs1(v, u);
		son[u] += son[v];
		if(son[heavy[u]] <= son[v]) heavy[u] = v;
	}
	return;
}

inline void dfs2(int u, int tp)
{
	top[u] = tp, dfn[u] = ++cnt;
	if(!heavy[u]) return;
	dfs2(heavy[u], tp);
	for(int i = head[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if(v == f[u] || v == heavy[u]) continue;
		dfs2(v, v);
	}
	return;
}

struct node
{
	int l, r, maxi;
}tree[N << 2];

inline void push_up(int x)
{
	tree[x].maxi = max(tree[x << 1].maxi, tree[x << 1 | 1].maxi);	
}

inline void build(int l, int r, int x)
{
	tree[x] = {l, r, 0};
	if(l == r) return (void) (tree[x].maxi = val[l]);
	int mid = l + r >> 1;
	build(l, mid, x << 1);
	build(mid + 1, r, x << 1 | 1);
	push_up(x);
	return;
}

inline void update(int pos, int k, int x)
{
	if(tree[x].l == tree[x].r && tree[x].r == pos)
		return (void) (tree[x].maxi = k);
	int mid = tree[x].l + tree[x].r >> 1;
	if(pos <= mid) update(pos, k, x << 1);
	if(pos > mid) update(pos, k, x << 1 | 1);
	push_up(x);
	return;
}

inline int query(int l, int r, int x)
{
	if(l <= tree[x].l && tree[x].r <= r) return tree[x].maxi;
	int mid = tree[x].l + tree[x].r >> 1, ans = 0;
	if(l <= mid) ans = max(ans, query(l, r, x << 1));
	if(r > mid) ans = max(ans, query(l, r, x << 1 | 1));
	return ans;
}

inline int ask(int x, int y)
{
	int ans = 0;
	while(top[x] != top[y])
	{
		if(dep[top[x]] < dep[top[y]]) swap(x, y);
		ans = max(ans, query(dfn[top[x]], dfn[x], 1));
		x = f[top[x]];
	}
	if(dfn[x] > dfn[y]) swap(x, y);
	return max(ans, query(dfn[x] + 1, dfn[y], 1));
}

signed main()
{
	ios :: sync_with_stdio(false);
	cin >> n;
	for(int i = 1; i < n; i++)
	{
		cin >> u >> v >> w;
		add_edge(u, v, w);
		add_edge(v, u, w);
	}
	dfs1(1, 0);
	dfs2(1, 1);
	for(int i = 1; i <= tot; i += 2)
	{
		int u = e[i].from, v = e[i].to;
		if(dep[u] < dep[v]) swap(u, v);
		val[dfn[u]] = e[i].val;
	}
	build(1, n, 1);
	while(cin >> s)
	{
		if(s == "DONE") break;
		cin >> a >> b;
		if(s == "CHANGE")
		{
			a = a * 2 - 1;
			int u = e[a].from, v = e[a].to;
			if(dep[u] < dep[v]) swap(u, v);
			update(dfn[u], b, 1);
		}
		else cout << (a == b ? 0 : ask(a, b)) << '\n';
	}
	return 0;
}

热门相关:恭喜你被逮捕了   买妻种田:山野夫君,强势宠!   豪门情变,渣总裁滚远点!   不科学御兽   未来兽世:买来的媳妇,不生崽