P4069 [SDOI2016]游戏 解题报告

这篇博客介绍了如何利用树剖和李超线段树的数据结构来解决一类树上路径的维护和查询问题。具体包括在给定树中对路径上点权的区间修改和区间查询,通过将问题转化为一次函数处理,实现了高效的解决方案。

P4069 [SDOI2016]游戏 解题报告

P4069 游戏

题目大意

给一棵树,点有点权。维护以下两种操作:

  1. 给出参数 s , t s,t s,t ,使路径 ( s , t ) (s,t) (s,t) 上的每个点 u u u 的点权对 a × d i s ( s , u ) + b a\times dis(s,u)+b a×dis(s,u)+b max ⁡ \max max
  2. 给出参数 s , t s,t s,t,询问路径 ( s , t ) (s,t) (s,t) 上点权最大值。

解题报告

如果是一个序列,我们发现这就是李超线段树的模板(区间修改,区间查询)。那么这题怎么办呢?其实只要套一层树剖。

随意钦定一个根,记 d i s ( u ) dis(u) dis(u) 表示到根距离。

s , t s,t s,t l c a lca lca p p p,则可以把操作1. 分成两部分:

  1. 在路径 ( s , p ) (s,p) (s,p) 上,有 y = a × d i s ( s , u ) + b = a ( d i s ( s ) − d i s ( u ) ) + b = − a ⋅ d i s ( u ) + a ⋅ d i s ( s ) + b y=a\times dis(s,u)+b=a(dis(s)-dis(u))+b=-a\cdot dis(u)+a\cdot dis(s)+b y=a×dis(s,u)+b=a(dis(s)dis(u))+b=adis(u)+adis(s)+b,可以看成是关于 d i s ( u ) dis(u) dis(u) 的一次函数。
  2. 在路径 ( p , t ) (p,t) (p,t) 上,有 y = a × d i s ( s , u ) + b = a ( d i s ( s ) + d i s ( u ) − 2 d i s ( p ) ) + b = a ⋅ d i s ( u ) + a ⋅ d i s ( s ) − 2 a ⋅ d i s ( p ) + b y=a\times dis(s,u)+b=a(dis(s)+dis(u)-2dis(p))+b=a\cdot dis(u)+a\cdot dis(s) - 2a\cdot dis(p)+b y=a×dis(s,u)+b=a(dis(s)+dis(u)2dis(p))+b=adis(u)+adis(s)2adis(p)+b 同样可以看成关于 d i s ( u ) dis(u) dis(u) 的一次函数。

于是树剖套李超线段树进行维护即可。这里用到李超线段树标记永久化的特点。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
char In[1 << 20], *ss = In, *tt = In;
#define getchar() (ss == tt && (tt = (ss = In) + fread(In, 1, 1 << 20, stdin), ss == tt) ? EOF : *ss++)
ll read() {
	ll x = 0, f = 1; char ch = getchar();
	for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;
	for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + int(ch - '0');
	return x * f;
}
const int MAXN = 1e5 + 5;
const int MAXM = 1e5 + 5;
const ll INF = 123456789123456789ll;
int n, m, head[MAXN], ver[MAXN << 1], nxt[MAXN << 1], cnt, dep[MAXN], fa[MAXN], son[MAXN], sz[MAXN], top[MAXN], dfn[MAXN], bel[MAXN], tim;
ll edg[MAXN << 1], dist[MAXN];
void addedge(int u, int v, ll w) {
	ver[++cnt] = v; nxt[cnt] = head[u]; edg[cnt] = w; head[u] = cnt;
}
void dfs1(int u, int f) {
	fa[u] = f; dep[u] = dep[f] + 1; sz[u] = 1; son[u] = 0;
	for(int i = head[u]; i; i = nxt[i]) if(ver[i] != f) {
		int v = ver[i]; dist[v] = dist[u] + edg[i];
		dfs1(v, u); sz[u] += sz[v];
		if(sz[v] > sz[son[u]]) son[u] = v;
	}
}
void dfs2(int u, int tprt) {
	top[u] = tprt; dfn[u] = ++tim; bel[tim] = u; if(son[u]) dfs2(son[u], tprt);
	for(int i = head[u]; i; i = nxt[i]) if(ver[i] != fa[u] && ver[i] != son[u]) {
		int v = ver[i]; dfs2(v, v);
	}
}
int Lca(int u, int v) {
	while(top[u] != top[v]) dep[top[u]] > dep[top[v]] ? u = fa[top[u]] : v = fa[top[v]];
	return dep[u] > dep[v] ? v : u;
}
#define ls p << 1
#define rs p << 1 | 1
struct Line {ll k, b;}li[MAXM << 5];
int tot, mn[MAXN << 2];
ll val[MAXN << 2];
void upd(int p) {val[p] = min(val[p], min(val[ls], val[rs]));}
ll calc(int id, int i) {return li[id].k * dist[bel[i]] + li[id].b;}
void build(int p, int l, int r) {
	mn[p] = 0; val[p] = min(calc(mn[p], l), calc(mn[p], r));
	if(l == r) return ;
	int m = (l + r) >> 1;
	build(ls, l, m); build(rs, m+1, r);
	upd(p);
}

void mdyrange(int p, int l, int r, int x, int y, int k) {
	if(x <= l && r <= y) {
		val[p] = min(val[p], min(calc(k, l), calc(k, r)));
		ll l1 = calc(k, l), l2 = calc(mn[p], l);
		ll r1 = calc(k, r), r2 = calc(mn[p], r);
		if(l1 <= l2 && r1 <= r2) {mn[p] = k; return;}
		if(l1 >= l2 && r1 >= r2) return ;
		int m = (l + r) >> 1;
		if(calc(k, m) < calc(mn[p], m)) swap(k, mn[p]);
		if(calc(k, l) < calc(mn[p], l)) mdyrange(ls, l, m, x, y, k);
		else mdyrange(rs, m+1, r, x, y, k);
		upd(p);
		return ;
	}
	int m = (l + r) >> 1;
	if(x <= m) mdyrange(ls, l, m, x, y, k);
	if(y > m) mdyrange(rs, m+1, r, x, y, k);
	upd(p);
}
ll qryrange(int p, int l, int r, int x, int y) {
	if(x <= l && r <= y) return val[p];
	int m = (l + r) >> 1;
	ll ans = min(calc(mn[p], x), calc(mn[p], y));
	if(y <= m) return min(ans, qryrange(ls, l, m, x, y));
	else if(x > m) return min(ans, qryrange(rs, m+1, r, x, y));
	else return min(ans, min(qryrange(ls, l, m, x, m), qryrange(rs, m+1, r, m+1, y)));
}
void mdyto(int u, int lca, int k) {
	while(top[u] != top[lca]) {
		mdyrange(1, 1, n, dfn[top[u]], dfn[u], k);
		u = fa[top[u]];
	}
	mdyrange(1, 1, n, dfn[lca], dfn[u], k);
}
ll qryto(int u, int lca) {
	ll ans = INF;
	while(top[u] != top[lca]) {
		ans = min(ans, qryrange(1, 1, n, dfn[top[u]], dfn[u]));
		u = fa[top[u]];
	}
	return min(ans, qryrange(1, 1, n, dfn[lca], dfn[u]));
}
#undef ls
#undef rs
int main() {
	n = read(); m = read();
	for(int i = 1; i < n; i++) {
		int u = read(), v = read(); ll w = read();
		addedge(u, v, w);
		addedge(v, u, w);
	}
	li[0] = Line{0, INF};
	dfs1(1, 0); dfs2(1, 1); build(1, 1, n);
	for(int i = 1; i <= m; i++) {
		int opt = read();
		if(opt == 1) {
			int s = read(), t = read(), lca = Lca(s, t);
			ll a = read(), b = read();
			li[++tot] = (Line){-a, b + a * dist[s]};
			mdyto(s, lca, tot);
			li[++tot] = (Line){a, a * dist[s] - 2 * a * dist[lca] + b};
			mdyto(t, lca, tot);
		} else {
			int s = read(), t = read(), lca = Lca(s, t);
			printf("%lld\n", min(qryto(s, lca), qryto(t, lca)));
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

日居月诸Rijuyuezhu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值