洛谷·[SDOI2016]游戏

初见安~这里是传送门:洛谷P4069 [SDOI2016]游戏

题解

这题真的是咕了好几个月了终于过了,然后又咕了几个星期才来写博客……

题意很简单,每次让你在一条路径上放一个等差数列,问你某点上数的最小值。

这就是一个李超线段树的模板题了。我们树上利用树剖开一棵线段树,维护每一段等差序列的最小值就好。

接下来看这一段一段的等差序列(直线)。

树上路径的经典性质——拆分成两段,s\rightarrow lcalca \rightarrow t。每次给定的a和b我们用k和b来替换。【y=kx+b

首先看前一段,设dis_i表示点i到根节点的距离,s\rightarrow lca路径上的点u将得到的数值是k*(dis_s-dis_u) + b,但是因为这条路是从下往上走的,而我们树剖的dfn序号是从上往下的,所以要反过来,所以这个等差序列应为:

k*(dis_s-dis_{LCA}) + b - k * (dis_u-dis_{LCA})=-k*dis_u+k*dis_s + b

也就是说,是个斜率为-kK=-k,B=k*dis_s+bB=k*dis_s+b的直线。直接插入就好。【注意,dis_u是自变量

 

接下来看后一段lca \rightarrow t,方向是顺着的所以可以直接写出来:

k*(dis_s-dis_{LCA})+b+k*(dis_u-dis_{LCA})=k*dis_u+k*(dis_s-2*dis_{LCA})+b

所以K=kB=k*(dis_s-2*dis_{LCA})+b

 

两种情况对应的解析式我们讨论出来了,接下来就是插入李超线段树然后该咋维护咋维护了。

上代码——

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#define maxn 100005
using namespace std;
typedef long long ll;
const ll INF = 123456789123456789;
int read() {
	int x = 0, f = 1, ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x * f;
}

int n, m;
struct edge {int to, w, nxt;} e[maxn << 1];
int head[maxn], k = 0;
void add(int u, int v, int w) {e[k] = {v, w, head[u]}; head[u] = k++;}

int size[maxn], dep[maxn], son[maxn], fa[maxn], top[maxn], dfn[maxn], raw[maxn], tot = 0;
ll dis[maxn];
struct HLD {//树剖基操打包
	void dfs1(int u) {
		size[u] = 1;
		for(int i = head[u], v; ~i; i = e[i].nxt) {
			v = e[i].to; if(v == fa[u]) continue;
			dep[v] = dep[u] + 1, fa[v] = u, dis[v] = dis[u] + 1ll * e[i].w;
			dfs1(v); size[u] += size[v];
			if(size[v] > size[son[u]]) son[u] = v;
		}
	}
	
	void dfs2(int u, int tp) {
		top[u] = tp; dfn[u] = ++tot, raw[tot] = u;
		if(son[u]) dfs2(son[u], tp);
		for(int i = head[u], v; ~i; i = e[i].nxt) {
			v = e[i].to; if(v != fa[u] && v != son[u]) dfs2(v, v);
		}
	}
	
	int LCA(int u, int v) {
		while(top[u] != top[v]) {
			if(dep[top[u]] > dep[top[v]]) swap(u, v);
			v = fa[top[v]];
		}
		if(dep[u] > dep[v]) return v; return u;
	}
}H;

struct TREE {ll k, b, minn;} t[maxn << 2];
void build(int p, int l, int r) {//建树
	t[p].b = t[p].minn = INF; 
	if(l == r) return;
	register int mid = l + r >> 1;
	build(p << 1, l, mid); build(p << 1 | 1, mid + 1, r);
}

void change(int p, int l, int r, int ls, int rs, ll k, ll b) {//李超线段树区间修改
	register int mid = l + r >> 1;//有点繁琐 但是很好理解
	if(ls <= l && r <= rs) {
		ll l1 = t[p].k * dis[raw[l]] + t[p].b, r1 = t[p].k * dis[raw[r]] + t[p].b;
		ll l2 = k * dis[raw[l]] + b, r2 = k * dis[raw[r]] + b;
		if(l1 <= l2 && r1 <= r2) return;
		if(l2 <= l1 && r2 <= r1) {t[p].k = k, t[p].b = b, t[p].minn = min(t[p].minn, min(l2, r2)); return;}
		ll mx = dis[raw[mid]], pp = (t[p].b - b) / (k - t[p].k);
		if(l1 < l2) {
			if(pp <= mx) change(p << 1, l, mid, ls, rs, t[p].k, t[p].b), t[p].k = k, t[p].b = b;
			else change(p << 1 | 1, mid + 1, r, ls, rs, k, b);
		} else {
			if(pp <= mx) change(p << 1, l, mid, ls, rs, k, b);
			else change(p << 1 | 1, mid + 1, r, ls, rs, t[p].k, t[p].b), t[p].k = k, t[p].b = b;
		}
		t[p].minn = min(t[p].minn, min(l2, r2));
		t[p].minn = min(t[p].minn, min(t[p << 1].minn, t[p << 1 | 1].minn)); return;
	}
	if(ls <= mid) change(p << 1, l, mid, ls, rs, k, b);
	if(rs > mid) change(p << 1 | 1, mid + 1, r, ls, rs, k, b);
	t[p].minn = min(t[p].minn, min(t[p << 1].minn, t[p << 1 | 1].minn));
}

void modify(int u, int v, ll k, ll b) {//树上跳出对应的区间
	while(top[u] != top[v]) {
		if(dep[top[u]] > dep[top[v]]) swap(u, v);
		change(1, 1, n, dfn[top[v]], dfn[v], k, b);
		v = fa[top[v]];
	}
	if(dep[u] > dep[v]) swap(u, v);
	change(1, 1, n, dfn[u], dfn[v], k, b);
}

ll ask(int p, int l, int r, int ls, int rs) {//李超线段树的区间查询
	if(ls <= l && r <= rs) return t[p].minn;
	register int mid = l + r >> 1; ll res = INF;
	if(t[p].b != INF) {
		register int ll = max(ls, l), rr = min(rs, r);
		res = min(t[p].k * dis[raw[ll]], t[p].k * dis[raw[rr]]) + t[p].b;
	}
	if(ls <= mid) res = min(res, ask(p << 1, l, mid, ls ,rs));
	if(rs > mid) res = min(res, ask(p << 1 | 1, mid + 1, r, ls, rs));
	return res;
}

ll query(int u, int v) {//树上跳区间
	ll ans = INF;
	while(top[u] != top[v]) {
		if(dep[top[u]] > dep[top[v]]) swap(u, v);
		ans = min(ans, ask(1, 1, n, dfn[top[v]], dfn[v]));
		v = fa[top[v]];
	}
	if(dep[u] > dep[v]) swap(u, v);
	return min(ans, ask(1, 1, n, dfn[u], dfn[v]));
}

signed main() {
	memset(head, -1, sizeof head);
	n = read(), m = read();
	for(int i = 1, u, v, w; i < n; i++) u = read(), v = read(), w = read(), add(u, v, w), add(v, u, w);
	H.dfs1(1), H.dfs2(1, 1); build(1, 1, n);
	
	register int op, s, t, a, b, lca;
	while(m--) {
		op = read(), s = read(), t = read();
		if(op == 2) printf("%lld\n", query(s, t));
		else a = read(), b = read(), lca = H.LCA(s, t), 
			modify(lca, s, -a, dis[s] * a + b), modify(lca, t, a, (dis[s] - 2 * dis[lca]) * a + b);
	}
	return 0;
}

迎评:)
——End——

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值