『树链剖分·树上倍增·线段树』遥远的国度

题目描述

在这里插入图片描述

题解

难点在于如何处理换根操作。我们可以分情况讨论。假设当前根节点为 r o o t root root,询问子树为 s o n 。 son。 son

  • x x x不在 r o o t root root 1 1 1的路径中时,我们发现答案不会产生变化,直接输出x的子树中的最小值即可。在这里,我们可以用树的DFS序的最小值即可。
  • 如果在这条路径中,我们发现答案就是除了这条链上x的儿子所在子树的所有节点的最小值。即 r o o t root root向上找 d e e p r o o t − d e e p x − 1 deep_{root}-deep_x-1 deeprootdeepx1次父亲的结果。这个操作我们可以用树上倍增来实现,一开始预处理出倍增数组即可。这与这个节点 y y y而言,这个点所在子树入股偶在 d f s dfs dfs序上是 L y , R y L_y,R_y Ly,Ry,那么答案就是 m i n ( [ 1 , L y − 1 ] , [ R y + 1 , n ] ) . min([1,L_y-1],[R_y+1,n]). min([1,Ly1],[Ry+1,n]).

然后就是比较模板的树链剖分了。

#include <bits/stdc++.h>

#define int long long

using namespace std;
const int N = 200000;

int n, m, tot = 0;
int dfn[N], L[N], R[N], dep[N];
int f[N][25], size[N], son[N], top[N], val[N];

vector <int> G[N];

struct seg {
	int l, r, sum, min, tag;
} a[N*10];

void dfs1(int x,int fa)
{
	size[x] = 1, dep[x] = dep[fa]+1;
	for (int i=0,y;i<G[x].size();++i) 
	{
		if ((y = G[x][i]) == fa) continue;
		f[y][0] = x, dfs1(y,x), size[x] += size[y];
		if (size[y] > size[son[x]]) son[x] = y;
	}
	return;
}

void dfs2(int x,int cur)
{
	dfn[L[x] = ++tot] = x, top[x] = cur;
	if (son[x] > 0) dfs2(son[x],cur);
	for (int i=0,y;i<G[x].size();++i)
	{
		y = G[x][i];
		if (y != f[x][0] && y != son[x]) dfs2(y,y);
	}
	R[x] = tot;
	return;
}

void DP(void)
{
	for (int j=1;j<=23;++j)
	    for (int i=1;i<=n;++i)
	        f[i][j] = f[f[i][j-1]][j-1];
	return;
}

void build(int p,int l,int r)
{
	a[p].l = l, a[p].r = r, a[p].min = LONG_LONG_MAX;
	if (l == r) {a[p].min = val[dfn[l]]; return;}
	int mid = l+r >> 1;
	build(p*2,l,mid);
	build(p*2+1,mid+1,r);
	a[p].min = min(a[p*2].min,a[p*2+1].min);
	return;
}

void spread(int p)
{
	if (a[p].tag == 0) return;
	a[p*2].tag = a[p*2+1].tag = a[p].tag;
	a[p*2].min = a[p*2+1].min = a[p].tag;
	a[p].tag = 0;
	return;
}

void change(int p,int l,int r,int v)
{
	if (l <= a[p].l && r >= a[p].r) {
		a[p].tag = a[p].min = v;
		return;
	}
	spread(p);
	int mid = a[p].l+a[p].r >> 1;
	if (l <= mid) change(p*2,l,r,v);
	if (r > mid) change(p*2+1,l,r,v);
	a[p].min = min(a[p*2].min,a[p*2+1].min);
	return;
}

int ask(int p,int l,int r)
{
	if (l <= a[p].l && r >= a[p].r) return a[p].min; 
	spread(p);
	int mid = a[p].l+a[p].r >> 1, res = LONG_LONG_MAX;
	if (l <= mid) res = min(res,ask(p*2,l,r));
	if (r > mid) res = min(res,ask(p*2+1,l,r));
	return res;
}

void modify(int x,int y,int v)
{
	for (; top[x] ^ top[y]; x = f[top[x]][0])
	{
		if (dep[top[x]] < dep[top[y]]) 
			x ^= y ^= x ^= y;
		change(1,L[top[x]],L[x],v);
	}
	if (dep[x] > dep[y]) x ^= y ^= x ^= y;
	change(1,L[x],L[y],v);
	return;
}

int getfa(int x,int y)
{
	for (int i=23;i>=0;--i)
	    if ((y >> i) & 1) x = f[x][i];
	return x;
}

signed main(void)
{
	scanf("%lld %lld", &n, &m);
	for (int i=1,x,y;i<n;++i)
	{
		scanf("%lld %lld", &x, &y);
		G[x].push_back(y);
		G[y].push_back(x);
	}
	for (int i=1;i<=n;++i)
		scanf("%lld", val+i);
	dfs1(1,0), dfs2(1,1), DP();
	build(1,1,n); 
	int root; scanf("%lld", &root);
	while (m --)
	{
		int opt; scanf("%lld", &opt);
		if (opt == 1) scanf("%lld", &root);
		if (opt == 2) {
			int x, y, v; scanf("%lld %lld %lld", &x, &y, &v);
			modify(x,y,v);
		}
		if (opt == 3) {
			int x, y, res = LONG_LONG_MAX; scanf("%lld", &x);
			if (x == root) {
				printf("%lld\n", a[1].min);
				continue;
			}
			if (dep[root] > dep[x] && x == f[ y = getfa(root,dep[root]-dep[x]-1) ][0]) {
				if (L[y] > 1) res = min(res,ask(1,1,L[y]-1));
				if (R[y] < n) res = min(res,ask(1,R[y]+1,n));
				printf("%lld\n", res);
			}
			else printf("%lld\n", ask(1,L[x],R[x]));
		}
	} 
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值