【bzoj2654】【tree】【二分+最小生成樹】
阿新 • • 發佈:2019-02-20
Description
給你一個無向帶權連通圖,每條邊是黑色或白色。讓你求一棵最小權的恰好有need條白色邊的生成樹。
題目保證有解。
Input
第一行V,E,need分別表示點數,邊數和需要的白色邊數。
接下來E行
每行s,t,c,col表示這邊的端點(點從0開始標號),邊權,顏色(0白色1黑色)。
Output
一行表示所求生成樹的邊權和。
Sample Input
2 2 10 1 1 1
0 1 2 0
Sample Output
2HINT
資料規模和約定
0:V<=10
1,2,3:V<=15
0,..,19:V<=50000,E<=100000
所有資料邊權為[1,100]中的正整數。
題解:顯然可以發現隨著白邊權值的增大。最小生成樹中白邊的個數不增。
然後根據這個性質我們就可以二分一個值,然後每次給白邊加上這個值。看一下最小生成樹中白邊的個數。
最後答案再把它減去。
看起來思路非常簡單,但是有一個很重要的細節。
如果在你的二分過程中如果給白邊加上mid,你得到的白邊數比need大。
給白邊加上mid+1,你得到的白邊比need小。
這種情況看似沒法處理。
但是考慮一下克魯斯卡爾的加邊順序。
可以發現如果出現這種情況,一定是有很多相等的白邊和黑邊。因為資料保證合法。
所以我們可以把一些白邊替換成黑邊。
所以我們要在白邊數>=need的時候跟新答案。
具體用ans=ans-mid*need;即可。
程式碼:
#include<cstdio> #include<algorithm> #include<iostream> using namespace std; int fa[1000001],n,m,need,l(-105),r(105),mid,temp,ans,r1,r2,cnt,ans2; struct use{int st,en,c,v;}e[100010]; int find(int x){if (x!=fa[x]) fa[x]=find(fa[x]);return fa[x];} bool cmp(use a,use b){if (a.v==b.v) return a.c<b.c;else return a.v<b.v;} void solve(){ sort(e+1,e+m+1,cmp); for (int i=1;cnt!=n-1;i++){ r1=find(e[i].st);r2=find(e[i].en); if (r1!=r2){fa[r1]=r2;cnt++;if (e[i].c==0) temp++;ans+=e[i].v;} } } int main(){ scanf("%d%d%d",&n,&m,&need); for (int i=1;i<=m;i++){ scanf("%d%d%d%d",&e[i].st,&e[i].en,&e[i].v,&e[i].c); e[i].st++;e[i].en++; } while (l<=r){ mid=(l+r)>>1; for (int i=1;i<=m;i++) {if (e[i].c==0) e[i].v+=mid;} for (int i=1;i<=n+1;i++) fa[i]=i;ans=0;temp=0;cnt=0; solve(); if (temp>=need) {l=mid+1;ans2=ans-need*mid;}else r=mid-1; for (int i=1;i<=m;i++) if (e[i].c==0) e[i].v-=mid; } printf("%d\n",ans2); }