【LOJ6405】「ICPC World Finals 2018」征服世界

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_39972971/article/details/88698592

【题目链接】

【思路要点】

  • 建议参考 W C 2019 WC2019 第一课堂陈江伦的《模拟费用流问题》课件。
  • 我们称需要军队的地方为老鼠,军队为洞,那么我们可以花费一定代价移动老鼠和洞,使得所有老鼠均进洞,我们需要最小化总代价。
  • 考虑使用贪心解决该问题,我们为每一只老鼠设定一个额外代价 -\infty ,其中 -\infty 是一个足够小的数,表示将该老鼠和某一个洞匹配后额外的代价。由于我们会最小化总代价,因此这样将保证所有老鼠均进洞,我们只需把最后的答案加上 + × M +\infty\times M ,其中 M M 为老鼠个数即可。
  • 记节点 i i 到根的距离为 d e p t h i depth_i ,树上 L c a Lca z z 的点 x , y x,y 之间的路径长度为 d e p t h x + d e p t h y 2 × d e p t h z depth_x+depth_y-2\times depth_z 。考虑在 z z 子树中的所有老鼠和洞,我们关心的仅仅是 d e p t h x , d e p t h y depth_x,depth_y 的数值,而不关心它们具体在哪里。不难发现,上述额外代价也可以直接与 d e p t h x depth_x d e p t h y depth_y 进行累加后看做老鼠和洞的固有属性,不妨记为 v a l u e x , v a l u e y value_x,value_y
  • 每当我们找到可以使当前总代价减小的匹配,即 v a l u e x + v a l u e y 2 × d e p t h z < 0 value_x+value_y-2\times depth_z<0 的匹配,匹配之,答案将会变优。但这样找到的匹配很可能不是最终答案上的匹配,因此我们需要提供一个反悔的可能。考虑撤销本次匹配的代价,我们可以重新计算得到新的 v a l u e x = 2 × d e p t h z v a l u e y , v a l u e y = 2 × d e p t h z v a l u e x value'_x=2\times depth_z-value'_y,value'_y=2\times depth_z-value_x
  • 至此,我们可以得出本题的算法,即从叶子结点出发,向上进行贪心。用两个小根堆来维护子树内 v a l u e x , v a l u e y value_x,value_y 的集合,在合并两棵子树的同时合并它们对应的堆。当堆顶元素满足 v a l u e x + v a l u e y 2 × d e p t h z < 0 value_x+value_y-2\times depth_z<0 时更新答案,并删除堆顶元素,加入 v a l u e x , v a l u e y value'_x,value'_y
  • 可以发现,若一对已经在 z z 处匹配的老鼠和洞同时反悔,那么 z z 的父边将会被老鼠正反经过两次,因此不反悔是更优的,从而不可能出现同时反悔的情况。那么,元素入堆的总次数将为 O ( X ) O(X) 级别,总时间复杂度 O ( X L o g X ) O(XLogX)
  • 可将堆中相同的元素并为一个,时间复杂度优化至 O ( N L o g N ) O(NLogN)

【代码】

#include<bits/stdc++.h>
#include<ext/pb_ds/priority_queue.hpp>
using namespace std;
const int MAXN = 3e5 + 5;
const long long INF = 1e12;
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("");
}
ll ans, depth[MAXN];
vector <pair <int, int>> a[MAXN];
int n, m, cnthole[MAXN], cntmouse[MAXN];
__gnu_pbds :: priority_queue <ll, greater<ll>> hole[MAXN], mouse[MAXN];
void work(int pos, int fa, int len) {
	depth[pos] = depth[fa] + len;
	while (cnthole[pos]--) hole[pos].push(depth[pos]);
	while (cntmouse[pos]--) mouse[pos].push(depth[pos] - INF);
	for (auto x : a[pos])
		if (x.first != fa) {
			work(x.first, pos, x.second);
			hole[pos].join(hole[x.first]);
			mouse[pos].join(mouse[x.first]);
		}
	while (!mouse[pos].empty() && !hole[pos].empty() && mouse[pos].top() + hole[pos].top() - 2 * depth[pos] < 0) {
		ll m = mouse[pos].top(), h = hole[pos].top(), s = m + h - 2 * depth[pos];
		ans += s; mouse[pos].pop(), hole[pos].pop();
		mouse[pos].push(m - s), hole[pos].push(h - s);
	}
}
int main() {
	read(n);
	for (int i = 1; i <= n - 1; i++) {
		int x, y, z; read(x), read(y), read(z);
		a[x].emplace_back(y, z);
		a[y].emplace_back(x, z);
	}
	for (int i = 1; i <= n; i++) {
		read(cnthole[i]), read(cntmouse[i]);
		int tmp = min(cnthole[i], cntmouse[i]);
		cnthole[i] -= tmp, m += cntmouse[i] -= tmp;
	}
	work(1, 0, 0);
	writeln(ans + INF * m);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_39972971/article/details/88698592