HDU多校第三场1010/HDU-6982 Road Discount【包含恰好k条黑边的最小生成树】

题目链接

思路

官方题解写的很清楚,将原边看成白边,折扣边看成黑边,求恰好黑边数为(k)的最小生成树。

这边有道一模一样的子问题BZOJ2654-tree.

我们需要设定一个(x)值,对每条黑边的长度算上(+x)的贡献,然后进行一次最小生成树,假如最小生成树的最终值为(sum),选择了(cnt)条黑边,那么最小生成树的值就是(sum-cnt*x)。因为题目所给的边的范围为([0,1000]),所以(x)的范围为([-1000,1000]).这样可以保证每一种黑边的情况。

在计算之前,首先可以把图缩小到(o(n))的级别,假如没有黑边,就是一个所有白边的最小生成树,没有白边,就是一个所有黑边的最小生成树。这样只有(O(2n))条边,那么对于最终黑白边混合的最小生成树,其所选的边一定是从这(2n)条边中选择。

同时我们可以发现,当(x)值越小的时候,选择的黑边就会越多,满足单调性。

对于寻找边数恰好为(k)的最小生成树,就有以下做法:

1、二分一个(x),将所有的黑边的权值(+x)

2、然后跑一遍最小生成树,记录最少需要黑边的数量(l_x)​,最多需要黑边的数量(r_x)

3、若对于一个(k)满足(l_xleq k leq r_x),那么此时得到的(sum-cnt*x)​可以作为答案。

在二分过程中每次查看返回的黑边数量是否满足要求即可。

对于BZOJ2654-tree这题因为只是输出一个答案,所以直接二分求值即可。对于此题需要先预处理出(x)(-1000)(1000)的所有情况,然后再去(klogn)的复杂度查询即可,发现标程这边偷懒写了(kn)的写法。

代码

#include<bits/stdc++.h>
using namespace std;

typedef pair<int, int> pii;
const int N = 1e3 + 10, M = 2e5 + 10;

struct Edge {
	int u, v, w;
	friend bool operator < (const Edge &a, const Edge &b) {
		return a.w < b.w;
	}
}e1[M], e2[M];
int fa[N];
int n, m;
pii f[N * 2];

int find(int x) {
	return x == fa[x] ? x : fa[x] = find(fa[x]);
}

void init_edge() {
	for(int i = 1; i <= n; i++) fa[i] = i;
	sort(e1 + 1, e1 + 1 + m);
	for(int i = 1, idx = 0; i <= m; i++) {
		int fx = find(e1[i].u), fy = find(e1[i].v);
		if(fx != fy) {
			fa[fx] = fy;
			e1[++idx] = e1[i];
		}
	}
	
	for(int i = 1; i <= n; i++) fa[i] = i;
	sort(e2 + 1, e2 + 1 + m);
	for(int i = 1, idx = 0; i <= m; i++) {
		int fx = find(e2[i].u), fy = find(e2[i].v);
		if(fx != fy) {
			fa[fx] = fy;
			e2[++idx] = e2[i];
		}
	}
}

bool merge(int x, int y) {
	int fx = find(x), fy = find(y);
	if(fx == fy) return 0;
	fa[fx] = fy;
	return 1;
}

pii calc(int x) {
	int l = 1, r = 1, cnt = 0, sum = 0;
	for(int i = 1; i <= n; i++) fa[i] = i;
	while(l < n && r < n) {
		if(e1[l].w > e2[r].w + x) {
			if(merge(e2[r].u, e2[r].v)) {
				sum += e2[r].w + x;
				cnt++;
			}
			r++;
		}
		else {
			if(merge(e1[l].u, e1[l].v)) {
				sum += e1[l].w;
			}
			l++;
		}
	}
	
	while(l < n) {
		if(merge(e1[l].u, e1[l].v)) {
			sum += e1[l].v;
		}
		l++;
	}
	while(r < n) {
		if(merge(e2[r].u, e2[r].v)) {
			sum += e2[r].w + x;
			cnt++;
		}
		r++;
	}
	return {sum, cnt};
}

int query(int x) {
	int l = -1000, r = 1000;
	int res = -1;
	while(l <= r) {
		int mid = l + r >> 1;
		if(f[mid + 1000].second <= x) {
			res = f[mid + 1000].first - x * mid;
			r = mid - 1;
		}
		else l = mid + 1;
	}
	return res;
}

void solve() {
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= m; i++) {
		int u, v, w1, w2;
		scanf("%d%d%d%d", &u, &v, &w1, &w2);
		e1[i] = {u, v, w1};
		e2[i] = {u, v, w2};
	}
	init_edge();
	for(int k = -1000; k <= 1000; k++) f[k + 1000] = calc(k);
	
	for(int i = 0; i <= n - 1; i++) {
		printf("%d
", query(i));
	}
}

int main() {
	int t; cin >> t; while(t--)
	solve();
	return 0;
}
原文地址:https://www.cnblogs.com/ZX-GO/p/15080374.html