bzoj2654 wqs二分

因为18南京的B去学习了一个

wqs二分是针对那种给你n个要你选k个使得最后答案最优(最大或最小),然后我对这每个物品增加一个权值,看他会选择几个,大于k就让权值变大(变大变小看情况),之后用dp算出这种情况下取了多少个物品,那么物品就会少选一点,小于k就让权值变小,那么就可以多选一点

几何解释可以看这个博客(虽然还是很难懂):https://www.cnblogs.com/CreeperLKF/p/9045491.html

在这道题中,最小生成树的值越小,收益越大,则g(x)越大,而白边越少,收益越小,白边越多,收益越少,收益函数是一个上凸包的形状,所以我们让白边增加的mid越小,也就是参考博客中的斜率越少,收益越少,白边越多(这个可以理性感受出来),让白边增加的mid越大,斜率越大,收益越少,所以我们通过二分mid,来让选择的白边数量等于need,从而得出g(need).

注意一个二分细节,我WA了很多次。就是我们要保证白边数量的单调性,在权值相等的边中,优先选择白边。还有一种特殊情况,即jug(mid)>need  jug(mid+1)<need的情况,我还在思考为什么我这种绝对不可能错的二分有问题。。。这个情况其实就是在mid时,有很多黑白一样权值的边,我们让一些白边变成权值一样的黑边,就可以变成need条白边,我们在退出二分判断l,r的时候,由于jug(r)<=jug(l)的,所以我们先判断jug(r)>=need ,否则答案是jug(l)>=need

#include<bits/stdc++.h>
#define maxl 100010
using namespace std;
 
int n,m,ans,sum,ned;
int f[maxl];
struct edge
{
    int u,v,col,val;
}e[maxl];
 
inline void prework()
{
    scanf("%d%d%d",&n,&m,&ned);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d%d",&e[i].u,&e[i].v,&e[i].val,&e[i].col);
        e[i].u++;e[i].v++;
    }
}
 
inline bool cmp(const edge &x,const edge &y)
{
    if(x.val==y.val)
        return x.col<y.col;
    return x.val<y.val;
}
 
inline int find(int x)
{
    if(f[x]!=x) 
        f[x]=find(f[x]);
    return f[x];
}
 
inline int jug(int mid)
{
    for(int i=1;i<=m;i++)
    if(e[i].col==0)
        e[i].val+=mid;
    for(int i=1;i<=n;i++)
        f[i]=i;
    sort(e+1,e+1+m,cmp);
    sum=0;int tot=0,x,y;
    for(int i=1;i<=m;i++)
    {
        x=find(e[i].u);y=find(e[i].v);
        if(x!=y)
        {
            f[y]=x;
            if(e[i].col==0) tot++;
            sum+=e[i].val;
        }
    }
    for(int i=1;i<=m;i++)
    if(e[i].col==0)
        e[i].val-=mid;
    return tot;
}
 
inline void mainwork()
{
    int l=-105,r=105,mid;
    while(l+1<r)
    {
        mid=(l+r)>>1;
        if(jug(mid)>=ned)
            ans=sum-mid*ned,l=mid;
        else
            r=mid;
    }
    if(jug(r)>=ned)
        ans=sum-r*ned;
    else
    if(jug(r-1)>=ned)
        ans=sum-(r-1)*ned;
    else
    if(jug(l-1)==ned)
        ans=sum-(l-1)*ned;
}
 
inline void print()
{
    printf("%d",ans);
}
 
int main()
{
    prework();
    mainwork();
    print();
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值