快来踩爆这个蒟蒻吧|

Little_corn

园龄:1年1个月粉丝:11关注:17

2024-06-01 16:27阅读: 21评论: 0推荐: 0

WQS 二分

WQS 二分适用于一类带凸性性质的 DP 方程的优化,可以快速的求出某一个状态的值。

引入:

  • Problem:n 个物品,恰好选出其中 m 个物品,最大化价值总和。

这个问题容易通过贪心解决。但是我们现在要考虑 DP 的做法。若我们令 fi,j 为考虑前 i 个物品,选了 m 个物品时最大价值总和。不难有 O(n2) 做法。

接下来是WQS 二分的优化方法,可以将算法时间复杂度优化到 O(nlogV)

我们令 fi 为恰好选了 i 个物品时的最大价值总和。

我们发现若没有 m 的限制,则该问题是简单的。

如果我们将所有的 (i,gi) 在平面坐标系上表示出来,不难发现它组成了一个上凸包的样子。此时我们拿一条斜率为 slope 的直线去切这个凸包,若第一个切到的点为 p,则其他的点都在 p 的下方。换句话说, p 点是凸包上的点中使该直线的截距最大的点。

于是我们进而考虑最大化该直线的截距,若我们取的点是 (p,gp),截距则为 c=gpslope×p。此时有关键的一步:不妨将所有的物品价值都减掉 slope。则问题被转化为了没有 m 的限制的情况。于是不难求出 p,以及对应的 gp

但是我们要求的是 m 的情况啊,那我们让直线切到的点是 (m,gm) 不就好了吗。由凸包的美好性质,不难发现当 pm 时应该减小斜率,否则增大斜率。那么这个东西具有单调性,用二分不难在 (logV) 时间解决。

例题:P5633 最小度限制生成树

显然答案具有下凸性,反证法不难证明。

于是我们二分斜率 slope,让所有跟 s 相连的边都减掉 slope,此时我们就把 k 的限制消掉了,做一遍 Kruskal 即可。

注意有一个特殊的情况:当凸包上有三点共线的情况时,我们可能切不到 k 所对应的点,而是切到两端的点,于是我们要尽量让选的边数尽量小。

代码注意细节。

code:

qwq
#include<bits/stdc++.h>
#define int long long
#define eq(i) ((edges[i].u == s) || (edges[i].v == s))
using namespace std;
const int N = 5e5 + 10;
struct edge{
int u, v, w;
}edges[N];
int n, m, s, k;
int fa[N], gp, res, cnt, inf = 5e4;
int findfa(int u){return fa[u] = (fa[u] == u) ? u : findfa(fa[u]);}
bool cmp(struct edge e1, struct edge e2){
if(e1.w != e2.w) return e1.w < e2.w;
return (e1.u == s) || (e1.v == s);//注意此处我们让含有 s 的尽量排在前面
}
void calc(int slope){
for(int i = 1; i <= n; i++) fa[i] = i;
for(int i = 1; i <= m; i++) if(eq(i)) edges[i].w -= slope;
sort(edges + 1, edges + m + 1, cmp); cnt = gp = res = 0;
for(int i = 1; i <= m; i++){
int fu = findfa(edges[i].u), fv = findfa(edges[i].v);
if(fu == fv) continue;
cnt++; gp += eq(i); res += edges[i].w;
fa[fu] = fv;
}
for(int i = 1; i <= m; i++) if(eq(i)) edges[i].w += slope;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin >> n >> m >> s >> k;
for(int i = 1; i <= m; i++){
int u, v, w; cin >> u >> v >> w;
edges[i] = {u, v, w};
}
calc(inf); if(k > gp){cout << "Impossible"; return 0;}
calc(-inf);if(k < gp){cout << "Impossible"; return 0;}
int l = -inf, r = inf;
while(l + 1 < r){
int mid = (l + r) >> 1; calc(mid);
if(gp < k) l = mid;
else r = mid;
}
calc(r); cout << res + r * k;
return 0;
}

本文作者:Little_corn

本文链接:https://www.cnblogs.com/little-corn/p/18226082

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Little_corn  阅读(21)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起