[BZOJ 2654]tree(陈立杰)
[BZOJ 2654]tree(陈立杰)
Description
给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有need条白色边的生成树。
题目保证有解。
Input
第一行V,E,need分别表示点数,边数和需要的白色边数。
接下来E行,每行s,t,c,col表示这边的端点(点从0开始标号),边权,颜色(0白色1黑色)。
Output
一行表示所求生成树的边权和。
Sample Input
2 2 1
0 1 1 1
0 1 2 0
0 1 1 1
0 1 2 0
Sample Output
2
Hint
V<=50000,E<=100000,所有数据边权为[1,100]中的正整数。
题解:
一道思维题,代码打起来实际上很简单。
二分+kruskal
如果直接kruskal求最小生成树,是无法保证白边数量的,那么我们考虑如果改变白边的数量。我们可以把白边全部都加上一个权值,也就是我们二分的值,然后跑最小生成树,同时记录白边数量。当白边数量>=need时,l=mid+1,否则r=mid−1,更新答案就是这棵生成树的权值和减去所有白边的增量。
证明:
我们发现,如果我们给白边增加权值,做最小生成树,由于白边权值增大,导致不容易选白边。记f(x)为给白边增加x(x可为负)权值,做最小生成树后,选白边的数量。可以发现,f(x)随x增大而减小,显然可以二分。
其次,我们发现,由于黑边的权值是不变的,与白边权值不相互影响。同样由于白边之间关系相对不变,必然选出的need条白边一定是符合题意的。
注意排序的时候如果权值相同要把白色的放在前面。
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<algorithm> using namespace std; int n,m,s,ans,mmin; struct node { int from,to,dis,color; }edge[100001]; bool cmp(const node a,const node b){if(a.dis!=b.dis)return a.dis<b.dis;else return a.color<b.color;} void change(int mid) { int i; for(i=1;i<=m;i++) if(!edge[i].color)edge[i].dis+=mid; } void change_back(int mid) { int i; for(i=1;i<=m;i++) if(!edge[i].color)edge[i].dis-=mid; } int father[50001]; int find(int x) { if(father[x]==x)return x; else return father[x]=find(father[x]); } bool judge(int mid) { int i,c=0; mmin=0; change(mid); sort(edge+1,edge+m+1,cmp); for(i=0;i<=n;i++)father[i]=i; for(i=1;i<=m;i++) { int p=find(edge[i].from),q=find(edge[i].to); if(p!=q) { father[p]=q; mmin+=edge[i].dis; if(edge[i].color==0)c++; } } change_back(mid); return c>=s; } int main() { int i,j; scanf("%d%d%d",&n,&m,&s); for(i=1;i<=m;i++) { scanf("%d%d%d%d",&edge[i].from,&edge[i].to,&edge[i].dis,&edge[i].color); } int l=-100,r=100,mid; while(l<=r) { mid=(l+r)>>1; if(judge(mid))ans=mmin-mid*s,l=mid+1; else r=mid-1; } printf("%d",ans); return 0; }