題解 #10069.【國家集訓隊Tree】
阿新 • • 發佈:2021-07-30
[國家集訓隊]Tree
題目大意:
給你一個無向帶權連通圖,每條邊是黑色或白色。讓你求一棵最小權的恰好有 \(need\) 條白色邊的生成樹。
solution:
考慮 \(\text{Kruskal}\) 的演算法流程:每次取邊權最小的邊添到生成樹裡,所以為了讓白色邊進入生成樹,我們可以將白色邊的邊權改變一下,但是改變多少呢?這個就可以用二分答案,再驗證。單調性後面證。
具體操作如下:
- 二分出當前 \(mid\) ,把所有白色邊的邊權都減去 \(mid\) 。
- 構建最小生成樹,記錄被加入到生成樹的白邊的個數,同時記錄答案。
- 若白邊個數 \(\geq need\) 則統計答案:\(ans=res+need \times mid\)
- 把所有邊權恢復,準備進行下一次二分。
單調性證明:
貪心的想,白色邊的邊權越小,則被加入到生成樹的邊數越多(顯然),這樣就滿足了單調性。
證畢[滑稽]。
細節處理:
- 這裡的白邊權有可能小導致生成樹中白邊過多,所以我們要增加權值,所以二分的左邊界 \(l\) 要從 \(-101\) (\(< -100\) 的數),右邊界 \(r\) 要取一個大於 \(100\) 的數。
- 答案 \(ans\) 一定要用最小生成樹的 \(res+\) 多減去的邊權,不可直接用 \(res+=need \times mid\) 。
感性理解下:
若倒數第二次的二分答案驗證成功,\(ans\)
程式碼
#include<cstdio> #include<algorithm> using namespace std; const int N=5e4+5,M=1e5+5; int n,m,need; struct B{ int x,y,z,c; }e[M]; inline bool cmp(B x,B y){ if(x.z==y.z) return x.c<y.c;//這也要注意:若此時邊權相等,優先選擇白色的邊 else return x.z<y.z; } int fa[N]; inline int find(int s){ return fa[s]==s?s:fa[s]=find(fa[s]); } int ans,res,num; inline void kru(){ sort(e+1,e+m+1,cmp); res=0,num=0; int cnt=0; for(int i=1;i<=m;i++){ int x=e[i].x,y=e[i].y,z=e[i].z; int col=e[i].c; x=find(x),y=find(y); if(x!=y){ fa[x]=y; if(!col) num++;//白色邊數++ res+=z; cnt++; } if(cnt==n-1) return ; } } int main(){ scanf("%d%d%d",&n,&m,&need); for(int i=1;i<=m;i++){ scanf("%d%d%d%d",&e[i].x,&e[i].y,&e[i].z,&e[i].c); e[i].x++,e[i].y++; } int l=-101,r=110; while(l<=r){ int mid=l+r>>1; for(int i=1;i<=n+1;i++) fa[i]=i; for(int i=1;i<=m;i++) if(!e[i].c) e[i].z-=mid; kru(); if(num>=need) {//答案可行,減少白邊數量 r=mid-1; ans=res+need*mid;//注 } else l=mid+1; for(int i=1;i<=m;i++) if(!e[i].c) e[i].z+=mid;//恢復邊權 } printf("%d",ans); return 0; }