Tree(Bzoj2624)
题目描述
原题来自:2012 年国家集训队互测
给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有 need\text{need}need 条白色边的生成树。题目保证有解。
输入格式
第一行 V,E,needV,E,\text{need}V,E,need 分别表示点数,边数和需要的白色边数。
接下来 EEE 行,每行 s,t,c,cols,t,c,\text{col}s,t,c,col 表示这边的端点(点从 000 开始标号),边权,颜色(000 白色,111 黑色)。
输出格式
一行表示所求生成树的边权和。
样例
样例输入
2 2 1
0 1 1 1
0 1 2 0
样例输出
2
很明显的最小生成树(题目里也是这么说的)
然而就调了一下午,就很慌,感觉离退役不远了
首先看一眼数据范围,暴力是O(n^2logn)的,用脚diao想也知道会T飞
哦们这时候就又要注意到题目中的一句话“让你求一棵最小权的恰好有 need\text{need}need 条白色边的生成树”
从我六年级的时候,老师就告诉我们,看到“最大值”或者“最小值”,一定要想二分,这就很明显了
但是二分答案并不好判断,所以我们通过枚举一个值来控制白边的数量
计算ans时只需要减去mid*need就可以了
下面给出代码:(注释就不写了,毕竟操作上没有难度)
#include<iostream> #include<algorithm> #include<cstdio> #include<cstdlib> #include<cstring> #include<string> #include<cmath> using namespace std; inline int rd(){ int x=0,f=1; char ch=getchar(); for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1; for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0'; return x*f; } inline void write(int x){ if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); return ; } struct node{ int u,v,w,c; }s[1000006],a[1000006]; bool cmp(node x,node y){ if(x.w==y.w) return x.c<y.c; return x.w<y.w; } int f[1000006]; int n,m; int need; int getf(int v){ if(f[v]==v) return v; return f[v]=getf(f[v]); } int ans=0; int sum=0; int solve(int x){ sum=0; int cnt=0; for(int i=1;i<=n;i++) f[i]=i; for(int i=1;i<=m;i++){ a[i]=s[i]; if(!a[i].c) a[i].w+=x; } sort(a+1,a+m+1,cmp); int num=0; for(int i=1;i<=m;i++){ int h1=getf(a[i].u),h2=getf(a[i].v); if(h1!=h2){ num++; f[h1]=h2; sum+=a[i].w; if(!a[i].c) cnt++; } if(num==n-1) break; } if(cnt>=need) return 1; return 0; } int main(){ n=rd(),m=rd(),need=rd(); for(int i=1;i<=m;i++){ s[i].u=rd(),s[i].v=rd(),s[i].w=rd(),s[i].c=rd(); s[i].u++; s[i].v++; } int cnt=0; int l=-106,r=106; while(l<=r){ int mid=(l+r)>>1; if(solve(mid)){ l=mid+1; ans=sum-need*mid; cnt++; } else r=mid-1; } printf("%d",ans); return 0; }
蒟蒻总是更懂你✿✿ヽ(°▽°)ノ✿