【LOJ574】「LibreOJ NOI Round #2」黄金矿工

【题目链接】

【思路要点】

  • 可参考 官方题解
  • 以下为笔者个人的见解,方便起见,下称矿工为老鼠,金矿为洞。
  • 我们可以对洞的权值加上深度,老鼠的权值减去深度,从而不需要考虑树的边权。
  • 考虑新加一只老鼠带来的影响,可能的结果有如下三种:
    ( 1 ) (1) (1) 、与一个尚未匹配的洞一起加入当前已经匹配的集合。
    ( 2 ) (2) (2) 、取代当前已经匹配的集合中的一只老鼠。
    ( 3 ) (3) (3) 、没有被加入匹配集合。
  • 这里我们考虑的匹配集合不需要考虑具体的匹配方案,而只需要保证存在至少一组可能的匹配方案。
  • 当一个权为 A A A 的老鼠和一个权为 B B B 的洞匹配时,我们可以认为在原来的老鼠处出现了一个权为 − A -A A 的洞,在原来的洞处出现了一个权为 − B -B B 的老鼠。
  • 可以发现,这样看待问题时,上述三种可能的结果本质上是统一的,即让当前老鼠找到一个能够匹配的权最大的洞匹配之,若这样做不能使答案增大,则放弃此次匹配。注意到匹配成功后新产生的洞是不会再次成功匹配的,否则之前的答案一定不是最优的。
  • 因此,我们现在只需要考虑 “找到一个能够匹配的权最大的洞” 的过程即可。
  • 对于老鼠出现的节点 x x x x x x 子树内的洞一定都能匹配,并且若 x x x 的父边被向下经过至少一次,那么向上经过这条边就可以看做转而调整经过这条边的另一个老鼠所匹配的洞。因此沿 x x x 的父边一直向上,直到到达的点的父边没有被向下经过过,该点子树内的所有洞即为能够匹配的范围。
  • 对于加入一个洞的过程,同样可以类似地考虑,即考虑只经过向上和被向下经过过的向下的边能够到达的老鼠。
  • 朴素地实现该算法,其时间复杂度为 O ( N × M + M L o g M ) O(N\times M+MLogM) O(N×M+MLogM) ,可以参考下列代码中 355 355 355 行后的内容。
  • 考虑采用树链剖分优化 “找到一个能够匹配的权最大的洞 / 老鼠” 的 过程。
  • 找洞的过程是较为容易的,只需在线段树上二分找到走到最上方的点即可。
  • 找老鼠的过程可以通过维护每个点所有轻儿子的子树中能够到达该点的老鼠的权值完成。
  • 时间复杂度 O ( N + Q L o g 2 N ) O(N+QLog^2N) O(N+QLog2N)

【代码】

// Program Till Line 355 
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const int INF = 1e9;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {
    x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {
    x = min(x, y); } 
template <typename T> void read(T &x) {
    
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
    
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
    
	write(x);
	puts("");
}
struct SegmentTree {
    
	struct Node {
    
		int lc, rc;
		int tag, Minf;
		pair <int, int> Maxh;
		pair <int, int> Maxm;
	} a[MAXN * 2];
	int n, root, size;
	void update(int root) {
    
		a[root].Minf = min(a[a[root].lc].Minf, a[a[root].rc].Minf);
		a[root].Maxh = max(a[a[root].lc].Maxh, a[a[root].rc].Maxh);
		a[root].Maxm = max(a[a[root].lc].Maxm, a[a[root].rc].Maxm);
	}
	void build(int &root, int l, int r) {
    
		root = ++size;
		if (l == r) {
    
			a[root].Maxh = make_pair(-INF, 0);
			a[root].Maxm = make_pair(-INF, 0);
			a[root].Minf = 0;
			return;
		}
		int mid = (l + r) / 2;
		build(a[root].lc, l, mid);
		build(a[root].rc, mid + 1, r);
		update(root);
	}
	void init(int x) {
    
		n = x;
		root = size = 0;
		build(root, 1, n);
	}
	void pushdown(int root) {
    
		if (a[root].tag) {
    
			a[a[root].lc].tag += a[root].tag;
			a[a[root].lc].Minf += a[root].tag;
			a[a[root].rc].tag += a[root].tag;
			a[a[root].rc].Minf += a[root].tag;
			a[root].tag = 0;
		}
	}
	void modifyf(int root, int l, int r, int ql, int qr, int d) {
    
		if (l == ql && r == qr) {
    
			a[root].Minf += d;
			a[root]
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值