洛谷 P2619/bzoj2654 tree

题目大意:给一个图G,其中有白边有黑边,求有need白边条边的最小生成树。
边小于100000条,点小于50000个,边权绝对值小于100;
题解:
看到题目肯定有贪心的想法,先贪心的取need条白边,在用黑边跑最小生成树。但是只要仔细一想就知道不行,因为白边的选取会影响到黑边的选取,很明显贪心是错误的。
考虑另一种计算方法,不管黑白边直接做最小生成树,有两种结果:
1、有多余need条白边被选。2、有少于need条白边被选。(等于直接输出就好了啊);
所以我们考虑怎么更改可以减少或增加白边的数量,并且对黑边的选择也是最优;我们可以将白边的值加上一个值x,这样对于白边的相对大小是没有改变的,与黑边构图时就会替换掉原图的一些黑边(或将白边替换为一些黑边),所以只要x的值恰当,就可以使得选的白边为need条,同时由于白边的相对大小没有改变,所以构建出来的图就是满足题意的生成树。由于x的值对于选择白边的数量具有单调性(x大白边少,反x小白边多),为了求出合适的x值,可以采用二分的方法,由于边权-100~+100,log(2,200)=8,复杂度为O(8*nlogn),显然可以通过;
细节:
1、对于二分出来的最终结果求边权而不是求二分过程中的最小边权,二分过程中的白边数不一定就是need;
2、黑边和白边权值一样时优先选白边,否则会使x值变大,导致答案错误;
3、如果x是最终结果且且选择了多于need条边,该答案是合法的;因为是最终二分结果,所以x-1的边数小于need(否则二分时缩小到x-1),这是说明在x时多选白边的数量大于了x-1时所差的白边数量,这些白边替换了原有的黑边且二者权值相同(因为x只增加了1就多出白边),即存在黑边可以替换这些白边且黑边数量=白边数(x)-白边数(x-1),所以用黑边替换一定可以使得白边的数量为need;
4、二分边界-100,100;
证明完毕;

#include<bits/stdc++.h>
using namespace std;
const int maxn=100000+10;
const int INF=1<<29;
struct Edge{
    int x,y,val,col;
}e[maxn];
inline int read()
{
    int ret=0;char c=getchar();
    while(c<'0'||c>'9') c=getchar();
    while(c>='0'&&c<='9')   ret=(ret<<1)+(ret<<3)+c-'0',c=getchar();
    return ret;
}
int n,m,need,flag,fa[maxn/2],ans=INF;
inline bool cmp(Edge a,Edge b){
    return (a.val+a.col*flag<b.val+b.col*flag)||
    (a.val+a.col*flag==b.val+b.col*flag&&a.col>b.col);
}
inline void init(int now)
{
    for(int i=0;i<=n;i++)   fa[i]=i;
    flag=now;
}
inline int find(int now)
{
    return fa[now]==now?now:fa[now]=find(fa[now]);
}
bool check(int now)
{
    int ned=0,ans_=0,cnt=0;
    init(now);
    sort(e+1,e+1+m,cmp);
    for(int i=1;i<=m;i++){
        int xx=find(e[i].x),yy=find(e[i].y);
        if(xx!=yy){
            fa[xx]=yy;ned+=e[i].col;
            ans_=ans_+e[i].val+e[i].col*now;
            cnt++;
        }
        if(cnt==n-1)    break;
    }
    if(ned>=need){
        ans=ans_-need*now;
        return true;
    }
    else return false;
}
int main()
{
    //freopen("bzoj2654.in","r",stdin);
    n=read(),m=read(),need=read();
    for(int i=1;i<=m;i++)
        e[i].x=read(),e[i].y=read(),e[i].val=read(),e[i].col=read()^1;
    int l=-100,r=100;
    while(l<=r){
        int mid=(l+r)>>1;
        if(check(mid))  l=mid+1;
        else r=mid-1;
    }
    printf("%d",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值