【BZOJ2654】Tree
题意
给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有k条白色边的生成树。
题目保证有解。
n<=50000,m<=100000
分析
第一想法是贪心,但是只考虑到贪心会让生成树可能不连通,于是开心地求了割边,把割边的顶点combine后再加白边,再加黑边。
WA到只有10分。因为本质还是在贪心,只要贪心地把黑白边分开就不对
比如只有三个顶点的一张图,有四条边,中间的一个顶点到另外两个顶点分别有一条白边一条黑边,此时限制只能加入一条白边。
假如到左边点的白边权值为1,黑边权值为3,到右边点的白边权值为2,黑边权值为10.
贪心,用左边的白边,那么不得不用右边的黑边,则sum=1+10=11>3+2=5.很明显贪心错误
虽然先求割边再分黑白跑Kruskal这是错误做法,但也有一定启示。在真正求生成树的时候,似乎已经有一些边被加进来了,导致已经产生了一定值
那么我们既然无法区分开黑白边,我们干脆忽略黑边过多的影响,仅使它们的权值对答案造成影响。
即对于每条白边,加上一个mid值(正负皆可)来控制数量(改变白边的排列顺序),暂且可理解为因为有黑边的加入所造成的答案偏移值。而边权范围为-100~100,很明显,二分这个偏移值
对于每次二分出来的答案,需要通过kruskal检验,如果用到的白边超过了限制,则需要加入更大的偏移值。
对于边权相同的黑白边,要把颜色相同的放在一起,否则影响正确性(影响白边数量单调性)
二分结束后的l不是答案!!需要跑一边kruskal带进去求出真正的sum,最后输出的答案要减去k条白边最后一次操作的偏移值
#include<cstdio> #include<algorithm> #include<queue> #include<cmath> #include<iostream> using namespace std; #define N 505000 int n,m,k,ans,sum; int fa[N],first[N],ui[N],vi[N],wi[N],ci[N]; struct email { int u,v,w,c; }e[N*10]; inline void add(int u,int v,int w,int c,int p) { e[p].u=u;e[p].v=v;e[p].w=w;e[p].c=c; } bool cmp(email a,email b) { if(a.w!=b.w) return a.w<b.w; return a.c<b.c; } inline int find(int x) { return fa[x]==x?x:fa[x]=find(fa[x]);} bool check(int mid) { int ans=0,num=0; for(int i=0;i<n;i++) fa[i]=i; for(int i=1;i<=m;i++) { add(ui[i],vi[i],wi[i],ci[i],i); if(!ci[i])e[i].w+=mid; } sort(e+1,e+1+m,cmp); sum=0; for(int i=1;i<=m;i++) { int u=e[i].u,v=e[i].v,w=e[i].w,c=e[i].c; int fax=find(u),fay=find(v); if(fax!=fay) { fa[fax]=fay; num++;sum+=w; if(!c)ans++; } if(num==n-1) break; } if(ans>=k) return true; return false; } int main() { scanf("%d%d%d",&n,&m,&k); for(int i=1;i<=m;i++) scanf("%d%d%d%d",&ui[i],&vi[i],&wi[i],&ci[i]); int mid,l=-100,r=100; while(l<r) { mid=l+r+1>>1; if(check(mid)) l=mid; else r=mid-1; } check(l); printf("%d\n",sum-k*l); return 0; }
“Make my parents proud,and impress the girl I like.”