1. 程式人生 > >Tree,noi.ac模擬賽,Hall定理

Tree,noi.ac模擬賽,Hall定理

正題

      題目連結點這裡

      最小值最大,想到二分。

      二分一個答案k,我們把>=k的邊全都刪掉,留下的邊把圖分成了很多個聯通塊,這些聯通塊如果可以連到外面去的話,那麼k就是滿足條件的(因為走到外面去都要經過>=k的邊啊

      那麼就相當於一張二分圖,左邊(X集合)是1到n,右邊(Y集合)有\sum_{i=1}^n x_i個節點,分別是x_1個1,x_2個2,...,x_n個n,第a個聯通塊內的節點連出去的邊數都是一樣的,\sum_{i=1}^n x_i-\sum_{i\in a}x_i(除去自己外其他聯通塊的xi的和

      那麼我們現在跑一次二分圖最大匹配就可以知道答案了!!

      判定二分圖的演算法除了匈牙利暴力和網路流你還會什麼:Hall定理

      Hall定理給出了一個解決本問題一個很好的思路:如果X集合內任選a個點,所連的邊都覆蓋Y集合內>=a個點,那麼這張二分圖內一定有一個完全匹配。

      好,我們來任選。

      又要超時。

      分情況討論:

      1.選的是不同聯通塊內的點:連出的邊覆蓋了Y集合內所有的點,又因為x_i>=1,所以\sum_{i=1}^{n}x_i>=n\rightarrow \begin{vmatrix} Y \end{vmatrix}>=\begin{vmatrix} X \end{vmatrix},所以右邊的點肯定不少於左邊選出來的點。

      2.選的是相同聯通塊內的點,那麼是不是要滿足對於每一個聯通塊的大小都不多於其他點的xi總和,size_i<=\sum_{j\notin i} x_j

      做完了。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

int n;
struct edge{
	int x,y,c;
	bool operator<(const edge x)const{
		return c<x.c;
	}
}s[100010];
int w[100010];
long long totw=0;
int f[100010],size[100010],wei[100010];

int findpa(int x){
	if(x!=f[x]) return f[x]=findpa(f[x]);
	return x; 
}

bool check(int x){
	for(int i=1;i<=n;i++) f[i]=i,size[i]=1,wei[i]=w[i];
	for(int i=1;i<=x;i++){
		int fx=findpa(s[i].x),fy=findpa(s[i].y);
		if(fx!=fy){
			if(size[fx]<size[fy]) swap(fx,fy);
			f[fy]=fx;
			size[fx]+=size[fy];
			wei[fx]+=wei[fy];
		}
	}
	for(int i=1;i<=n;i++){
		int fx=findpa(i);
		if(size[fx]>totw-wei[fx]) return false;
	}
	return true;
}

int main(){
	scanf("%d",&n);
	for(int i=1;i<=n-1;i++) scanf("%d %d %d",&s[i].x,&s[i].y,&s[i].c);
	for(int i=1;i<=n;i++) scanf("%d",&w[i]),totw+=w[i];
	sort(s+1,s+n);
	int ans=0;
	int l=1,r=n-1;
	while(l<=r){
		int mid=(l+r)/2;
		if(check(mid)){
			ans=mid;
			l=mid+1;
		}
		else r=mid-1;
	}
	printf("%d\n",s[ans+1].c);
}