wqs 二分

也叫带权二分,用于解决恰好选 k 个物品的最优代价的问题,基于答案关于选的物品个数的函数的凸性。只要凸就行,上凸还是下凸并不重要。

二分的写法与函数凸的方向无关,而和问题是最大化还是最小化有关。

如果问题是最小化,那么当斜率增加,选一件物品的代价减小,显然我们会选择更多的物品。当斜率减少,选一件物品的代价增大,我们选的物品个数就会减少。也就是说,当当前选的物品个超过了限制,我们会增大二分的斜率。反之若当前选的物品还不够,则会减小当前二分的斜率。

如果问题是最大化,那么当斜率增加,选一件物品的代价减小,显然我们会选择更少的物品。当斜率减少,选一件物品的代价增大,我们选的物品个数就会增多。也就是说,当当前选的物品个超过了限制,我们会减小二分的斜率。反之若当前选的物品还不够,则会增大当前二分的斜率。

至于答案那个点和相邻的点共线的情况,我们可以每次 check 时让选的物品尽量少,然后就可以正常写。最后算答案的时候最终截距加上的应该是限制选的个数乘以最终斜率。

P5633 最小度限制生成树

有解容易做,判无解的时候可以利用 wqs 二分中的 check 函数,通过调整 check 的参数为 ± 可以求出任意生成树中最少和最多包含 s 的几条邻边。然后随便做。

代码
#include <iostream>
#include <algorithm>
#include <cassert>
#define int long long
using namespace std;
const int inf = 10000000;
int n, m, s, K, d;
struct Edge {
    int u, v, w, c;
} e[500005];
int dsu[100005];
int getf(int x) { return (dsu[x] == x ? x : (dsu[x] = getf(dsu[x]))); }
pair<int, int> chk(int x) {
    for (int i = 1; i <= m; i++) e[i].w -= e[i].c * x;
    sort(e + 1, e + m + 1, [](Edge a, Edge b) { return (a.w == b.w) ? (a.c > b.c) : (a.w < b.w); });
    int ret = 0, cnt = 0;
    for (int i = 1; i <= n; i++) dsu[i] = i;
    int r = n - 1;
    for (int i = 1; i <= m && r; i++) {
        int u = getf(e[i].u), v = getf(e[i].v);
        if (u != v) 
            --r, cnt += e[i].c, ret += e[i].w, dsu[u] = v;
    }
    for (int i = 1; i <= m; i++) e[i].w += e[i].c * x;
    if (r != 0) {
        cout << "Impossible\n";
        exit(0);
    }
    return make_pair(ret, cnt);
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> m >> s >> K;
    for (int i = 1; i <= m; i++) cin >> e[i].u >> e[i].v >> e[i].w, e[i].c = (e[i].u == s || e[i].v == s), d += e[i].c;
    int mn = chk(-inf).second, mx = chk(inf).second;
    if (K < mn || mx < K) {
        cout << "Impossible\n";
        return 0;
    }
    int l = -inf, r = inf, mid, ans = 0;
    while (l <= r) {
        mid = (l + r) >> 1;
        if (chk(mid).second >= K) 
            ans = mid, r = mid - 1;
        else 
            l = mid + 1;
    }
    pair<int, int> tmp = chk(ans);
    cout << chk(ans).first + K * ans << "\n";
    return 0;
}

至多 / 至少 k

还是答案关于选的物品个数的函数是凸的,这个时候限制在平面上体现为一条竖线左 / 右的区域都可选。一般 wqs 二分只能做恰好 k 个,所以需要先处理一下。先通过 check(0) 求出原问题(不带权)的最优解,如果这个最优解已经在可选区域内,那就做完了。否则根据答案函数的凸性,这个时候我们恰好选 k 个就会是最优的。因此就转化为恰好 k 个的问题,直接做就好了。

也可以在二分开始时将斜率的上 / 下界从 ± 改成 0 这样的,这样相当于直接把 k 和最优情况下选的物品段数取 min(或取 max)。

posted @   forgotmyhandle  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示