wqs 二分

前言

WC 将我暴打。

wqs 二分

黑龙江神仙 OIer 王钦石 归纳的一类二分方式,由于是 [IOI2016]ailens 的正解于是在国外被称为 "Alien trick"生动体现了 wqs 二分是多么魔法

考虑对于一类特殊的问题 :有限制的最优化。

抽象一个模型,\(n\) 个数选出恰好 \(k\) 个,可能还有其他限制,最大化权值和。

令 \(F(x)\) 表示选恰好 \(x\) 个时得到的最大价值,然后显然直接求所有方案是 \(\dbinom{n}{x}\) 的,类似背包那样的 DP 是平方的 ,但是如果画出 \(F(x)\) 关于 \(x\) 的图像满足上凸性,就可以依照选择的数量进行特殊的加权,使用 wqs 二分。

同理最小化就要求是下凸的。

考虑首先二分斜率然后找到这个斜率的凸包的切线,因为图像上凸,斜率逐渐增大,切点就逐渐向右移。

于是这个过程是单峰的。

我们先不考虑斜率,先考虑求出一个斜率的时候如何求切点。

显然同一斜率时仅仅有切点使得 \(y\) 轴截距最小。

那么就是求每个物品贡献都减去了定值时的最优化问题,直接贪心/简单 DP 即可。

例题

P2619 [国家集训队]Tree I

wqs 本人给出的一个例题。

首先对于使用白色边数量有限制,那么考虑对于所有白色边加/减一个权值使得恰好有要求的边数即可。

int n,m;
struct MST {
	int st,ed,w,ty;
	inline bool operator < (const MST &oth) const {
		if(w != oth.w) return w < oth.w;
		return ty > oth.ty;
	}
}e[N << 1];

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

inline pii Kruskal(int x,int mx = INF) {
	rep(i,1,m) e[i].w += e[i].ty * x;
	std::sort(e + 1,e + m + 1);
	repl(i,0,n) dsu[i] = i;
	int cnt = 0,ans = 0,et = 0;
	rep(i,1,m) {
		int fx = find(e[i].st);
		int fy = find(e[i].ed);
		if(fx == fy) continue;
		if(e[i].ty && !mx) continue;
		mx -= e[i].ty;
		dsu[fx] = fy;
		cnt += e[i].ty;
		ans += e[i].w;
		if(++et == (n - 1))
			break;
	}
	rep(i,1,m) e[i].w -= e[i].ty * x;
	return std::make_pair(cnt,ans);
}

int main() {
    init_IO();
	n = read(),m = read();int ne = read();
	rep(i,1,m) {
		e[i].st = read();
		e[i].ed = read();
		e[i].w = read();
		e[i].ty = 1 - read();
	}
	int l = -101,r = 101,ans = 0;
	while(l <= r) {
		int mid = (l + r) >> 1;
		pii res = Kruskal(mid);
		if(res.first >= ne) {
			ans = res.second - mid * ne;
			l = mid + 1;
		}
		else r = mid - 1;
	}
	write(ans),enter;
    end_IO();
    return 0;
}

P5633 最小度限制生成树

概念差不多的一个题。

最开始我有那么 1 秒认为是对每个点都指定度了,吓我一跳

为一端为 \(s\) 的边赋予权值即可,甚至特判比上一个题要少。

然后就是特判一下无解就行。

int n,m;
struct MST {
	int st,ed,w,ty;
	inline bool operator < (const MST &oth) const {
		if(w != oth.w) return w < oth.w;
		return ty > oth.ty;
	}
}e[M];

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

inline pii Kruskal(int x,int mx = INF) {
	rep(i,1,m) e[i].w += e[i].ty * x;
	std::sort(e + 1,e + m + 1);
	rep(i,1,n) dsu[i] = i;
	int cnt = 0,ans = 0,et = 0;
	rep(i,1,m) {
		int fx = find(e[i].st);
		int fy = find(e[i].ed);
		if(fx == fy) continue;
		if(e[i].ty && !mx) continue;
		mx -= e[i].ty;
		dsu[fx] = fy;
		cnt += e[i].ty;
		ans += e[i].w;
		if(++et == (n - 1))
			break;
	}
	rep(i,1,m) e[i].w -= e[i].ty * x;
	return std::make_pair(cnt,ans);
}

int main() {
    init_IO();
	n = read(),m = read();int s = read(),ne = read();
	rep(i,1,m) {
		e[i].st = read();
		e[i].ed = read();
		e[i].w = read();
		e[i].ty = (e[i].st == s || e[i].ed == s);
	}
	int l = -30005,r = 30005,ans = 0;
	if(Kruskal(l).first < ne || Kruskal(r).first > ne) {
		putStr("Impossible");
		end_IO();
		return 0;
	}
	while(l <= r) {
		int mid = (l + r) >> 1;
		pii res = Kruskal(mid);
		if(res.first >= ne) {
			ans = res.second - mid * ne;
			l = mid + 1;
		}
		else r = mid - 1;
	}
	write(ans),enter;
    end_IO();
    return 0;
}
上一篇:51单片机:8位数码管动态显示,每次按S1键加1


下一篇:知识点案例回顾总结