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;
}

浙公网安备 33010602011771号