Tree,noi.ac模拟赛,Hall定理

版权声明:因为我是蒟蒻,所以请大佬和神犇们不要转载(有坑)的文章,并指出问题,谢谢 https://blog.csdn.net/Deep_Kevin/article/details/83478462

正题

      题目链接点这里

      最小值最大,想到二分。

      二分一个答案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);
}

猜你喜欢

转载自blog.csdn.net/Deep_Kevin/article/details/83478462