wqs二分学习笔记

新科技 Get

设想这样一类问题,要求从 \(n\) 个物品中选择某 \(m\) 个,其中满足某种限制的必须有恰好 \(k\),同时最优化答案。

这样看上去很棘手的问题可以用 wqs 二分轻松解决。懒得画图,懂的都懂(

首先给模板题

求无向图的最小生成树,每条边为黑边或白边,要求有恰好 \(x\) 条白边。

首先设 \(f(i)\) 表示 \(x=i\) 时候的答案,求得最小生成树,设它含有的白边数量为 \(t\),那显然 \(f(t)\) 是最小的。

同时 \(i\)\(t\) 越远,\(f(i)\) 也会不断增大,所以将 \(f(i)\) 的图像画出来是一个下凸壳的形状,即斜率递增的图像。

现在需要求的是 \(f(x)\),考虑二分斜率,若每次都能根据斜率求出对应切点的横纵坐标,就能够解决问题。

根据切点的性质,当前斜率 \(k\) 经过这个点得到的直线的截距一定是最大的。

\(g(i)\) 表示截距,有 \(g(i)=f(i)-k\times i\),它也可以表示为,每个白边减去 \(k\) 后,没有限制的最小生成树。

并且在生成树过程中,可以同时得到 \(i\),那么也就得到了 \(f(i)\),于是就都解决了。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 5e4 + 10;
const int M = 1e5 + 10;
int n, m, k, ans, fa[N], head[N];
struct Edge{int u, v, w, c;} e[M];

int read(){
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? - 1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}

bool cmp(Edge a, Edge b) {
	return a.w != b.w ? a.w < b.w : a.c < b.c;
}

int Get(int x) {
	if(x == fa[x]) return x;
	return fa[x] = Get(fa[x]);
}

bool Chck(int mid) {
	for(int i = 1; i <= m; i ++) if(e[i].c == 0) e[i].w -= mid;
	sort(e + 1, e + m + 1, cmp);
	for(int i = 1; i <= n; i ++) fa[i] = i; 
	int cnt = 0; ans = 0;
	for(int i = 1; i <= m; i ++) {
		int u = Get(e[i].u), v = Get(e[i].v);
		int w = e[i].w, c = e[i].c;
		if(u != v) {
			ans += w;
			cnt += (c == 0);
			fa[u] = v;
		}
	}
	for(int i = 1; i <= m; i ++) if(e[i].c == 0) e[i].w += mid;
	return cnt >= k;
}

int main() {
	n = read(), m = read(), k = read();
	for(int i = 1; i <= m; i ++) {
		int u = read() + 1, v = read() + 1;
		int w = read(), c = read();
		e[i] = (Edge){u, v, w, c};
	}
	int l = - 100, r = 100;
	while(l < r) {
		int mid = (l + r) >> 1;
		if(Chck(mid)) r = mid;
		else l = mid + 1;
	}
	Chck(l);
	printf("%d\n", ans + k * l);
	return 0;
}

结尾彩蛋:

  1. wqs二分题单,不过这种思想了解一道也差不多都会了 QwQ
  2. 神仙's blog
posted @ 2021-09-13 16:11  LPF'sBlog  阅读(59)  评论(0编辑  收藏  举报