[提高组集训2021] 木示木干

一、题目

两棵 \(n\) 个点的有根树,\(1\) 是这两棵树的根,这两棵树中叶子个数相等。

然后对于第一棵树的每一个叶子找出不同的第二棵树的叶子与之配对,连上 \(+\infty\) 的边。

找到删除边的边权和最小的删边方案使得:

  • 两棵树根不连通。
  • 两棵树所在联通分量是一棵树。
  • 两棵树的并集是全集。

\(n\leq 10^5,w\leq 10^9\)

二、解法

因为限制二的存在,一定不要往最小割方向想,走不通的!

简单的转化:最小的删边方案等价于最大的保留边方案。因为最后剩下的是两棵树所以保留边很舒服,为了进一步简化问题我们将两个根先连起来,然后做最大生成树,最后再把这条边拆掉就完美地满足了限制。

现在有一个暴力算法是枚举所有匹配之后跑最大生成树。但是稍微有一点做题经验的人就知道我们决策完美匹配是不可能的,但是这个限制有存在,这时候就拿出我们的暴力满足限制法

也就是我们先不看限制,再求出一定能满足限制的最优解。设 \(m\) 为一棵树的叶子数,那么忽略限制的话我们会求出只有 \(m+1\) 个连通块的答案,结论:如果 \(m+1\) 个连通块中都有叶子那么最后可以满足限制,证明:

  • 必要性:如果一个连通块内没有叶子那么一定连不在一起。
  • 充分性:考虑归纳证明,如果 \(m=1\) 的情况显然,对于 \(m>1\) 的情况,根据抽屉原理一定有一个连通块只有一个叶子,那么找到另一个有两个叶子而且可连的连通块连起来可以归纳到 \(m-1\) 的情况。

那么直接跑最大生成树即可,时间复杂度 \(O(n\log n)\)

#include <cstdio>
#include <algorithm>
using namespace std;
const int M = 200005;
#define ll long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,k,e,fa[M],d[M],a[M];ll ans;
struct node
{
	int u,v,c;
	bool operator < (const node &b) const
	{
		return c>b.c;
	}
}s[M];
int find(int x)
{
	if(x!=fa[x]) fa[x]=find(fa[x]);
	return fa[x];
}
void uni(int x,int y)
{
	int u=find(x),v=find(y);
	k-=(a[v]&a[u]);
	a[v]|=a[u];fa[u]=v;
} 
signed main()
{
	freopen("C.in","r",stdin);
	freopen("C.out","w",stdout);
	n=read();
	for(int i=1;i<=2*n;i++)
		fa[i]=i;
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read(),c=read();
		s[++e]=node{u,v,c};d[u]++;d[v]++;ans+=c;
	}
	for(int i=1;i<n;i++)
	{
		int u=read()+n,v=read()+n,c=read();
		s[++e]=node{u,v,c};d[u]++;d[v]++;ans+=c;
	}
	for(int i=1;i<=2*n;i++)
		if(i!=1 && i!=n+1 && d[i]==1)
			a[i]=1,k++;
	m=k/2;uni(1,n+1);
	sort(s+1,s+1+e);
	for(int i=1;i<=e;i++)
	{
		int u=s[i].u,v=s[i].v,x=find(u),y=find(v);
		if(x==y) continue;
		if(a[x] && a[y] && k==m+1) continue;
		uni(u,v);
		ans-=s[i].c;
	}
	printf("%lld\n",ans);
}
posted @ 2021-07-30 22:29  C202044zxy  阅读(51)  评论(0编辑  收藏  举报