前言
WC 将我暴打。
wqs 二分
黑龙江神仙 OIer 王钦石 归纳的一类二分方式,由于是 [IOI2016]ailens 的正解于是在国外被称为 "Alien trick"生动体现了 wqs 二分是多么魔法
考虑对于一类特殊的问题 :有限制的最优化。
抽象一个模型,\(n\) 个数选出恰好 \(k\) 个,可能还有其他限制,最大化权值和。
令 \(F(x)\) 表示选恰好 \(x\) 个时得到的最大价值,然后显然直接求所有方案是 \(\dbinom{n}{x}\) 的,类似背包那样的 DP 是平方的 ,但是如果画出 \(F(x)\) 关于 \(x\) 的图像满足上凸性,就可以依照选择的数量进行特殊的加权,使用 wqs 二分。
同理最小化就要求是下凸的。
考虑首先二分斜率然后找到这个斜率的凸包的切线,因为图像上凸,斜率逐渐增大,切点就逐渐向右移。
于是这个过程是单峰的。
我们先不考虑斜率,先考虑求出一个斜率的时候如何求切点。
显然同一斜率时仅仅有切点使得 \(y\) 轴截距最小。
那么就是求每个物品贡献都减去了定值时的最优化问题,直接贪心/简单 DP 即可。
例题
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;
}
概念差不多的一个题。
最开始我有那么 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;
}