图论训练之十二

夙愿已清 提交于 2019-12-01 19:21:16

bzOJ2654: tree

Description

给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有need条白色边的生成树。
题目保证有解。
Input

第一行V,E,need分别表示点数,边数和需要的白色边数。
接下来E行,每行s,t,c,col表示这边的端点(点从0开始标号),边权,颜色(0白色1黑色)
Output

一行表示所求生成树的边权和。
V<=50000,E<=100000,所有数据边权为[1,100]中的正整数。
Sample Input

2 2 1
0 1 1 1
0 1 2 0
Sample Output

2

复习一下wqs二分

思路:wqs二分
显而易见,这是一道最小生成树......
但是问题在于其中有黑白边及白边大小上的要求。

根据kruskal的做法,我们发现选边的优先极取决于边权

如果我们能够将白边的边权进行扩大(缩小),那么就可以做到控制白边数量了。

接着,思考:当把所有白边都加上一个偏移量offset之后,可以求出一棵最小生成树,

再将答案减去树中**所有白边的数量*offset,其实这就是一种合法的生成树。
offset过大导致选择的
白边太少反之则太多**。

我们发现白边数量随offset的递增而递增,因此我们可以二分答案,二分offset的大小求解

code:
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
const int MAXE=100010;
struct edge
{
    int u,v,w,c;
}E[MAXE];
int v,e,need,wn,ans,tot;
int f[MAXE/2];
int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
bool cmp(edge a,edge b){return a.w==b.w?a.c<b.c:a.w<b.w;}
int kruskal(int add)
{
    for(int i=1;i<=v;i++)f[i]=i;
    for(int i=1;i<=e;i++)
        if(!E[i].c)E[i].w+=add;
    sort(E+1,E+1+e,cmp);
    int cnt=0,w=0;tot=0;
    for(int i=1;i<=e;i++)
    {
        if(find(E[i].u)!=find(E[i].v))
        {
            f[find(E[i].u)]=find(E[i].v);
            cnt++;tot+=E[i].w;
            if(!E[i].c)w++;
        }
        if(cnt==v-1)break;
    }
    for(int i=1;i<=e;i++)
        if(!E[i].c)E[i].w-=add;
    return w;
}
int main()
{
    cin>>v>>e>>need;
    for(int i=1;i<=e;i++)
    {
        cin>>E[i].u>>E[i].v>>E[i].w>>E[i].c;
        E[i].u++;E[i].v++;
        if(!E[i].c)wn++;
    }
    int l=-100,r=100;
    while(l<r)
    {
        int mid=(l+r)>>1;
        if(kruskal(mid)>=need){l=mid+1;ans=tot-wn*mid;}
        else r=mid-1;
    }
    cout<<ans<<endl;
    return 0;
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!