【9.30】wqs二分
wqs二分
wqs二分常用来解决如下问题:
给定若干 \(n\) 个物品,要求从中恰好选 \(m\) 次,最大化/最小化选的物品的权值和。
使用条件:
- 设 \(g(i)\) 选 \(i\) 个物品的最优方案,将所有 \((i,g(i))\) 的点画出来,必须组成一个
凸包(上凸包、下凸包均可)。
因为是凸包,对应性质为斜率 递增/递减
题目特点:
- 如果不限制选的个数,那么很容易求出最优方案。(所以一般用来优化 \(dp\))
- 选的物品越多,权值越 大/小。
判断能否使用的方法:
- 打表看 \((i,g(i))\) 拟合出的图形是凸包。
- 满足两个特点。
感性理解。感觉可以用WQS二分就大胆用WQS二分。
剩下的去看详解吧。
题单: https://www.luogu.com.cn/training/612052
P2619 [国家集训队] Tree I
给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有 𝑛𝑒𝑒𝑑 条白色边的生成树。
题目保证有解。
选 \(x\) 条白边,其答案为 \(g(x)\) ,根据最小生成树的定义,函数 \(g(x)\) 是一个上凸函数。
考虑将所有白边加上 \(k\) , \(k\) 即是直线的斜率,显然,随着 \(k\) 的增大,在最小生成树中选取的白边数量会减小。问题转换成求一个恰当的 \(k\) ,使直线与凸包相切于 \((need,g(need))\) 。
#include <bits/stdc++.h>
#define MAXV 50003
#define MAXE 100003
using namespace std;
int V,E,need;
struct egde{
int s,t,val,col;
bool operator<(const egde &b)const{
if (val==b.val) return col<b.col;
else return val<b.val;
}
}e[MAXE];
int temp,sum;
int f[MAXV];
int find(int x){
while (x!=f[x]) x=f[x]=f[f[x]];
return x;
}
void kruskal(){ //在白边都加上mid后跑一遍kruskal
//求得当前的最小生成树中有多少白边
sort(e+1,e+1+E);
for (int i=1,cnt=1;i<=E&&cnt<=V-1;i++){
int fs=find(e[i].s),ft=find(e[i].t);
if (fs!=ft){
cnt++;
f[fs]=ft; sum+=e[i].val;
if (e[i].col==0) temp++;
}
}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>V>>E>>need;
for (int i=1;i<=E;i++){
cin>>e[i].s>>e[i].t>>e[i].val>>e[i].col;
e[i].s++; e[i].t++;
}
int l=-114,r=114; int ans=0; //值域(白边加权)即二分的范围
while (l<r){
int mid=(l+r)>>1;
for (int i=1;i<=E;i++) if (e[i].col==0) e[i].val+=mid;
for (int i=1;i<=V;i++) f[i]=i;
temp=0; sum=0;
kruskal();
if (temp>=need) {l=mid+1; ans=sum-mid*need;} //白边过多,增加mid
else r=mid;
for (int i=1;i<=E;i++) if (e[i].col==0) e[i].val-=mid;
}
cout<<ans<<endl; //over
return 0;
}

浙公网安备 33010602011771号