【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;
}
posted @ 2024-10-11 21:48  EcapsXD  阅读(39)  评论(0)    收藏  举报