[SDOI2016] 游戏(树链剖分+李超线段树)

该博客主要介绍了如何利用树链剖分和李超线段树来解决一个关于游戏的动态更新问题。在n,m不超过100000且绝对值不超过10000的条件下,通过分析路径上的距离关系,将问题简化为线段形式。博主详细解析了如何构建树链剖分,并在每个节点上维护区间最小值的李超线段树,最终实现O(nlog^4n)的时间复杂度解法,实测运行效率高。" 117330692,10914930,Java大厂面试必问:Spring Cloud、Redis与MySQL实战,"['Java', '后端', '微服务', 'Spring框架', '数据库']

题意

在这里插入图片描述
其中,n,m≤100000,∣a∣≤10000n,m\leq 100000,|a|\leq 10000n,m100000,a10000

分析

iii 到根的距离为 disidis_idisi
首先化简一下式子,从 ssslca(s,t)lca(s,t)lca(s,t) 路径:
add=a×(diss−disi)+b=−a×disi+(b+a×diss)add=a\times (dis_s-dis_i)+b=-a\times dis_i+(b+a\times dis_s)add=a×(dissdisi)+b=a×disi+(b+a×diss)
可以看出是一条线段的形式,xxx 坐标是 disdisdis。从 lca(s,t)lca(s,t)lca(s,t)ttt 同理。
然后,考虑树链剖分,这样对于一条重链的区间,它的 disdisdis 是递增的。
因此,这个问题就变得明显了起来,就是树链剖分后用李超线段树加线段。
不过,这里的李超线段树每个节点还要维护一个区间的最小值。考虑李超线段树的性质,这个东西是可以维护的,就是每次用最优线段去尝试更新这个区间的最值(最值肯定在两端取到),同时,还要像普通线段树那样从子节点向父节点更新。
时间复杂度是 O(nlog4n)O(nlog^4n)O(nlog4n) 的,但是由于树剖和李超树的常数很小,可以在 1s1s1s 内跑过。

代码如下

#include <bits/stdc++.h>
#include <ext/pb_ds/hash_policy.hpp>
#include <ext/pb_ds/assoc_container.hpp>
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1
#define int long long
using namespace __gnu_pbds;
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;

LL z = 1;

int ksm(int a, int b, int p){
	int s = 1;
	while(b){
		if(b & 1) s = z * s * a % p;
		a = z * a * a % p;
		b >>= 1;
	}
	return s;
}

const int N = 1e5 + 5, inf = 123456789123456789;
struct node{
	int a, b, c, n;
}d[N * 2];
int h[N], son[N], dfn[N], siz[N], top[N], fa[N], dep[N], dis[N], dft, re[N], n, cnt;

int tag[N * 4], K[N * 2], B[N * 2], mn[N * 4], tot;

int get(int p, int x){
	return K[p] * dis[re[x]] + B[p];
}

void cr(int a, int b, int c){
	d[++cnt] = {a, b, c, h[a]}, h[a] = cnt;
}

void dfs1(int a){
	siz[a] = 1;
	for(int i = h[a]; i; i = d[i].n){
		int b = d[i].b, c = d[i].c;
		if(b == fa[a]) continue;
		fa[b] = a;
		dis[b] = dis[a] + c;
		dep[b] = dep[a] + 1;
		dfs1(b);
		siz[a] += siz[b];
		if(siz[b] >= siz[son[a]]) son[a] = b;
	}
}

void dfs2(int a, int f){
	top[a] = f, dfn[a] = ++dft, re[dft] = a;
	if(son[a]) dfs2(son[a], f);
	for(int i = h[a]; i; i = d[i].n){
		int b = d[i].b;
		if(b != fa[a] && b != son[a]) dfs2(b, b);
	}
}

int lca(int a, int b){
	int f1 = top[a], f2 = top[b];
	while(f1 != f2){
		if(dep[f1] < dep[f2]) swap(f1, f2), swap(a, b);
		a = fa[f1], f1 = top[a];
	}
	return dep[a] < dep[b]? a: b;
}

void build(int l, int r, int rt){
	tag[rt] = 1, mn[rt] = inf;
	if(l == r) return;
	int m = l + r >> 1;
	build(lson);
	build(rson);
}

void update(int l, int r, int rt, int a, int b, int u){
	int m = l + r >> 1, &v = tag[rt];
	if(l >= a && r <= b){
		if(get(u, m) < get(v, m)) swap(u, v);
		mn[rt] = min(mn[rt], min(get(v, l), get(v, r)));//更新 mn[rt] 
		if(l == r) return;
		if(get(u, l) < get(v, l)) update(lson, a, b, u);
		else if(get(u, r) < get(v, r)) update(rson, a, b, u);
		mn[rt] = min(mn[rt], min(mn[rt << 1], mn[rt << 1 | 1]));//比普通李超树就多了这个 
		return;	
	}
	if(a <= m) update(lson, a, b, u);
	if(b > m) update(rson, a, b, u);
	mn[rt] = min(mn[rt], min(mn[rt << 1], mn[rt << 1 | 1]));//比普通李超树就多了这个 
}

int query(int l, int r, int rt, int a, int b){
	if(l >= a && r <= b) return mn[rt];//如果到达某一被覆盖的节点,直接返回最值 
	int ans = min(get(tag[rt], max(a, l)), get(tag[rt], min(r, b))), m = l + r >> 1;//注意这里的实际区间是 [max(a, l), min(b, r)],用当前节点的优势线段求一下两端 
	if(a <= m) ans = min(ans, query(lson, a, b));
	if(b > m) ans = min(ans, query(rson, a, b));
	return ans;
}

void add(int a, int b, int p){
	int f1 = top[a], f2 = top[b];
	while(f1 != f2){
		update(1, n, 1, dfn[f1], dfn[a], p);//往重链加线段 
		a = fa[f1], f1 = top[a];
	}
	update(1, n, 1, dfn[b], dfn[a], p);//往重链加线段 
}

int find(int a, int b){
	int f1 = top[a], f2 = top[b], ans = inf;
	while(f1 != f2){
		if(dep[f1] < dep[f2]) swap(a, b), swap(f1, f2);
		ans = min(ans, query(1, n, 1, dfn[f1], dfn[a]));
		a = fa[f1], f1 = top[a];
	}
	if(dep[a] < dep[b]) swap(a, b);
	ans = min(ans, query(1, n, 1, dfn[b], dfn[a]));
	return ans;
}

main(){
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	
	int m;
	cin >> n >> m;
	for(int i = 1; i < n; i++){
		int a, b, c;
		cin >> a >> b >> c;
		cr(a, b, c);
		cr(b, a, c);
	}
	
	dfs1(1);
	dfs2(1, 1);
	
	B[tot = 1] = inf;
	build(1, n, 1);//初始化线段树的每个节点 
	
	for(int i = 1; i <= m; i++){
		int o;
		cin >> o;
		if(o == 1){
			int s, t, a, b;
			cin >> s >> t >> a >> b;
			int ff = lca(s, t);
			K[++tot] = -a, B[tot] = b + a * dis[s];//两种情况 
			add(s, ff, tot);
			K[++tot] = a, B[tot] = b + a * dis[s] - a * 2 * dis[ff];
			add(t, ff, tot); 
		}
		else{
			int s, t;
			cin >> s >> t;
			cout << find(s, t) << '\n';
		}
	}
	return 0;
}

# P4069 [SDOI2016] 游戏 ## 题目描述 Alice 和 Bob 在玩一个游戏游戏在一棵有 $n$ 个点的树上进行。最初,每个点上都只有一个数字,那个数字是 $123456789123456789$。 有时,Alice 会选择一条从 $s$ 到 $t$ 的路径,在这条路径上的每一个点上都添加一个数字。对于路径上的一个点 $r$,若 $r$ 与 $s$ 的距离是 $dis$,那么 Alice 在点 $r$ 上添加的数字是 $a\times dis+b$。 有时,Bob 会选择一条从 $s$ 到 $t$ 的路径。他需要先从这条路径上选择一个点,再从那个点上选择一个数字。 Bob 选择的数字越小越好,但大量的数字让 Bob 眼花缭乱。Bob 需要你帮他找出他能够选择的最小的数字。 ## 输入格式 第一行两个数字 $n,m$,表示树的点数和进行的操作数。 接下来 $n-1$ 行,每行三个数字 $u,v,w$,表示树上有一条连接 $u,v$ 的边,长度是 $w$。 接下来 $m$ 行。每行第一个数字是 $1$ 或 $2$。 若第一个数是 $1$,表示 Alice 进行操作,接下来四个数字 $s,t,a,b$。 若第一个数是 $2$,表示 Bob 进行操作,接下来两个数字 $s,t$。 ## 输出格式 每当 Bob 进行操作,输出一行一个数,表示他能够选择的最小的数字 ## 输入输出样例 #1 ### 输入 #1 ``` 3 5 1 2 10 2 3 20 2 1 3 1 2 3 5 6 2 2 3 1 2 3 -5 -6 2 2 3 ``` ### 输出 #1 ``` 123456789123456789 6 -106 ``` ## 说明/提示 测试点 1 ~ 2:$ n \leq 10 $,$ m \leq 10 $,$ | a | \leq 10000 $; 测试点 3 ~ 4:$ n \leq 1000 $,$ m \leq 1000 $,$ | a | \leq 10000 $; 测试点 5:$ n \leq 100000 $,$ m \leq 100000 $,$ a = 0 $,树是一条链; 测试点 6 ~ 7:$ n \leq 100000 $,$ m \leq 100000 $,$ a = 0 $; 测试点 8:$ n \leq 100000 $,$ m \leq 100000 $,$ a = 1 $,树是一条链; 测试点 9 ~ 10:$ n \leq 100000 $,$ m \leq 100000 $,$ a = 1 $; 测试点 11 ~ 13:$ n \leq 100000 $,$ m \leq 100000 $,$ | a | \leq 10000 $,树是一条链; 测试点 14 ~ 20:$ n \leq 100000 $,$ m \leq 100000 $,$ | a | \leq 10000 $。 对于所有数据,$0\le w, |b|\le 10^9$。 过了样例,为什么0分 ```cpp #include <iostream> #include <algorithm> #include <vector> using namespace std; #define int long long const int INF = 123456789123456789; const int MAX_N = 200050; struct Edge {int v, w; }; int n, m, tim, fa[MAX_N], dis[MAX_N]; int dep[MAX_N], siz[MAX_N], son[MAX_N]; int top[MAX_N], dfn[MAX_N], rep[MAX_N]; vector<Edge> e[MAX_N]; void dfs1(int u, int father, int depth, int dist) { fa[u] = father; dep[u] = depth; siz[u] = 1; dis[u] = dist; for (Edge edge : e[u]) { int v = edge.v; if (v == father) continue; dfs1(v, u, depth + 1, dist + edge.w); siz[u] += siz[v]; if (siz[v] > siz[son[u]]) son[u] = v; } } void dfs2(int u, int topf) { top[u] = topf; dfn[u] = ++tim; rep[tim] = u; if (son[u]) dfs2(son[u], topf); for (Edge edge : e[u]) if (!dfn[edge.v]) dfs2(edge.v, edge.v); } int LCA(int x, int y) { while (top[x] != top[y]) { if (dep[top[x]] < dep[top[y]]) swap(x, y); x = fa[top[x]]; } if (dep[x] < dep[y]) swap(x, y); return y; } struct Line { int k, b; } line[MAX_N]; inline int get(int idx, int x) { return line[idx].k * dis[x] + line[idx].b; } int tot, tr[MAX_N << 2], mn[MAX_N << 2]; #define ls(cur) cur << 1 #define rs(cur) cur << 1 | 1 void pushup(int cur, int l, int r) { int mid = l + r >> 1; mn[cur] = min({get(tr[cur], mid), mn[ls(cur)], mn[rs(cur)]}); } void build(int cur, int l, int r) { mn[cur] = INF; if (l == r) return ; int mid = l + r >> 1; build(ls(cur), l, mid); build(rs(cur), mid + 1, r); } void update(int cur, int l, int r, int idx) { int mid = l + r >> 1; if (get(idx, mid) < get(tr[cur], mid)) swap(idx, tr[cur]); if (l == r) { mn[cur] = get(tr[cur], mid); return ; } if (get(idx, l) < get(tr[cur], l)) update(ls(cur), l, mid, idx); if (get(idx, r) < get(tr[cur], r)) update(rs(cur), mid + 1, r, idx); pushup(cur, l, r); } void modify(int cur, int l, int r, int L, int R, int idx) { if (L <= l && r <= R) { update(cur, l, r, idx); return ; } int mid = l + r >> 1; if (L <= mid) modify(ls(cur), l, mid, L, R, idx); if (mid + 1 <= R) modify(rs(cur), mid + 1, r, L, R, idx); pushup(cur, l, r); } int query(int cur, int l, int r, int L, int R) { int mid = l + r >> 1; if (L <= l && r <= R) return mn[cur]; int res = INF; if (L <= mid) res = min(res, query(ls(cur), l, mid, L, R)); if (mid + 1 <= R) res = min(res, query(rs(cur), mid + 1, r, L, R)); return res; } void add(int x, int y, int k, int b) { line[++tot] = Line{k, b}; while (top[x] != top[y]) { modify(1, 1, n, dfn[top[x]], dfn[x], tot); x = fa[top[x]]; } modify(1, 1, n, dfn[y], dfn[x], tot); } int query(int x, int y) { int res = INF; while (top[x] != top[y]) { if (dep[top[x]] < dep[top[y]]) swap(x, y); res = min(res, query(1, 1, n, dfn[top[x]], dfn[x])); x = fa[top[x]]; } if (dep[x] < dep[y]) swap(x, y); res = min(res, query(1, 1, n, dfn[y], dfn[x])); return res; } signed main() { cin >> n >> m; for (int i = 1, u, v, w; i < n; i++) { cin >> u >> v >> w; e[u].push_back(Edge{v, w}); e[v].push_back(Edge{u, w}); } dfs1(1, 0, 1, 0); dfs2(1, 0); line[0].b = INF; build(1, 1, n); for (int opt, s, t, a, b; m--; ) { cin >> opt >> s >> t; if (opt == 1) { cin >> a >> b; int lca = LCA(s, t); add(s, lca, -a, b + a * dis[s]); add(t, lca, a, b + a * (dis[s] - 2 * dis[lca])); } else cout << query(s, t) << '\n'; } return 0; } ```
最新发布
08-25
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值