WQS 二分

用处

用来解决一些恰好选了 \(m\) 个的问题。

应用

就比如在 \(n\) 个物品中选出 \(m\) 个物品的最大值。

虽然可以直接贪,但还是考虑用 dp 来解。

\(f_{i,j}\) 表示前 \(i\) 个数,选择了 \(j\) 数的最大值。

转移 \(O(nm)\),考虑优化,但是发现转移已经是 \(O(1)\) 了,优化状态数量又不可能。

WQS 二分凭空出世。

其他博客写得函数看不懂,所以直接感性理解。

\(g(x)\) 为恰好选了 \(x\) 个物品的最大值,那么答案就是 \(g(m)\)

首先通过这个题可以知道 \(g(x)-g(x-1)\ge g(x+1)-g(x)\),很显然,每次多选一个数,最大值的增量肯定会减小(因为大的肯定都优先选择了)。

考虑直接做没有个数限制的 dp,当然这样跑出来可能会大于 \(m\) 或小于 \(m\) 个。

所以考虑二分一个值 \(k\),考虑给所有物品的值减去 \(k\),然后跑 dp。如果选择的物品个数小于 \(m\) 那么 \(k\) 的值就要减小,\(k\) 要减小是因为如果 \(k\) 值越小,那么每个物品的值就越大,愿意选择的物品就更多;如果物品个数大于 \(m\)\(k\) 的值就要增加,这里同理;如果等于,那么 dp 出来的值加上 \(k\times m\) 就是答案。

这里能够二分 \(k\) 的前提就是要满足上面说的 \(g(x)-g(x-1)\ge g(x+1)-g(x)\),证明,感性理解。

然后就可以在 \(O(n\log V)\) 的复杂度做出这道题。

题目

种树

这里贪心就不好做了,所以考虑 dp。

\(f_{i,j,0/1}\) 表示前 \(i\) 个物品选了 \(j\) 个,第 \(i\) 个物品选不选的最大值。

复杂度 \(O(nm)\) 的。

考虑 WQS 二分优化。

但是这里求的是至多选 \(m\) 个物品,怎么办?

其实可以先跑一遍 \(k=0\) 的无限制 dp,如果说选的物品个数已经小于 \(m\),那么直接输出答案即可。

否则就说明必须选恰好 \(m\) 个物品才能取得最大值,那么直接 WQS 二分就行。

这里说一些细节,dp 时,在最大值一样的情况时,尽量多选择物品,这样显然不劣。

代码
#include <bits/stdc++.h>
#define int long long

void Freopen() {
    freopen("", "r", stdin);
    freopen("", "w", stdout);
}

using namespace std;
const int N = 3e5 + 10, M = 2e5 + 10, inf = 1e18, mod = 998244353;

int n, m;
int a[N];
int f[N][2], g[N][2];

pair< int, int> dp( int k) {
    for ( int i = 0; i <= n; i ++)
        for ( int op = 0; op < 2; op ++)
            f[i][op] = g[i][op] = -inf;

    f[0][0] = g[0][0] = 0;

    for ( int i = 1; i <= n; i ++) {
        if (f[i - 1][1] > f[i - 1][0]) f[i][0] = f[i - 1][1], g[i][0] = g[i - 1][1];
        else if (f[i - 1][1] < f[i - 1][0]) f[i][0] = f[i - 1][0], g[i][0] = g[i - 1][0];
        else f[i][0] = f[i - 1][1], g[i][0] = max(g[i - 1][1], g[i - 1][0]);
        
        f[i][1] = f[i - 1][0] + a[i] - k;
        g[i][1] = g[i - 1][0] + 1;
    }

    if (f[n][1] > f[n][0]) return {f[n][1], g[n][1]};
    else if (f[n][1] < f[n][0]) return {f[n][0], g[n][0]};
    else return {f[n][1], max(g[n][1], g[n][0])};
}

signed main() {
    ios :: sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    cin >> n >> m;
    for ( int i = 1; i <= n; i ++) cin >> a[i];

    auto res = dp(0);
    if (res.second < m) return cout << res.first << '\n', 0;

    int l = -inf, r = inf, ans;

    while (l < r) {
        int mid = ((l + r + 1) >> 1);

        auto res = dp(mid);

        if (res.second >= m) l = mid;
        else r = mid - 1;
    }

    cout << dp(l).first + l * m << '\n';

    return 0;
}

[国家集训队] Tree I

要求白边个数恰好为 \(need\) 时的最小生成树。

这里求最小,那么考虑给边权加 \(k\),最后答案减 \(k\times need\)

还是一样的,要求选的白边个数尽可能大。

代码
#include <bits/stdc++.h>

void Freopen() {
    freopen("", "r", stdin);
    freopen("", "w", stdout);
}

using namespace std;
const int N = 2e5 + 10, M = 2e5 + 10, inf = 1e9, mod = 998244353;

int n, m, nd;

struct edge {
    int u, v, w, op;
} E[N];

int fa[N];

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

pair< int, int> dp( int k) {
    for ( int i = 1; i <= m; i ++)
        if (E[i].op == 0) E[i].w += k;

    for ( int i = 1; i <= n; i ++) fa[i] = i;

    sort(E + 1, E + m + 1, [&]( edge a, edge b) {
        return a.w == b.w ? a.op < b.op : a.w < b.w;
    });
    // 排序时,边权一样的边,白边优先,这样就满足白边尽可能多。

    int cnt = 0, cnt0 = 0, ans = 0;

    for ( int i = 1; i <= m; i ++) {
        auto [u, v, w, op] = E[i];
        u = getf(u), v = getf(v);

        if (u == v) continue ;
        fa[u] = v;

        cnt ++;
        cnt0 += (op == 0);
        ans += w;

        if (cnt == n - 1) break ;
    }

    for ( int i = 1; i <= m; i ++)
        if (E[i].op == 0) E[i].w -= k;

    return {ans, cnt0};
}

signed main() {
    ios :: sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    cin >> n >> m >> nd;

    for ( int i = 1; i <= m; i ++) {
        int u, v, w, op; cin >> u >> v >> w >> op;
        u ++, v ++;
        E[i] = {u, v, w, op};
    }

    int l = -100, r = 100;

    while (l < r) {
        int mid = (l + r + 1) >> 1;

        auto res = dp(mid);
        if (res.second >= nd) l = mid;
        else r = mid - 1;
    }

    cout << dp(l).first - nd * l << '\n';

    return 0;
}
posted @ 2025-08-26 21:24  咚咚的锵  阅读(8)  评论(0)    收藏  举报