BZOJ2067 SZN

题目大意

传送门(yzhx在写这篇题解的时候bzoj崩了,只能挂这个了)

给定一颗 n 个点的树,节点间距离为1, 求最少链覆盖,以及使在最少链覆盖的前提下最长链最短

题解

先求第一问:

根据贪心的思想考虑每一个非树根节点,
显然它可以选择一条连向儿子的边归为连向父亲的边所在的那条链
(儿子数为奇数时必须选一条,偶数时可以不选,但其实这在第一问不重要),
所以每个非树根节点的贡献是 儿子数量/2,
而根节点若儿子数量为奇,则必须多生成一条链
也就是 (儿子数量+1)/2

再求第二问

记录f[i]表示从底部开始算,第i号节点已经累积的未匹配的距离(也就是第一问中有奇数个儿子的叶子必须找出一条与父亲边组成链那种情况的累积长度)
每次二分出最大值,然后遍历整棵树check即可
遍历过程中值得注意的是: 对于每个节点,把它的每个儿子j按f[j]排序,然后按最大+最小,次大加次小组合起来,若有不合法或奇数个,则需二分出哪条边要向上合并

代码

#include<bits/stdc++.h>
using namespace std;
#define re register
#define ll long long
#define in inline
#define get getchar()
in int read()
{
	int t=0; char ch=get;
	while(ch<'0' || ch>'9') ch=get;
	while(ch<='9' && ch>='0') t=t*10+ch-'0', ch=get;
	return t;
}
const int _=1e5+6;
struct edge{
	int to,ne;
}e[_];
int h[_],n,tot,f[_],c[_],son[_];
in void add(int x,int y)
{
	e[++tot].to=y,e[tot].ne=h[x],h[x]=tot;
}
in int check1(int l,int r,int x,int lim)
{
	while(l<r)
	{
		if(l==x)l++;if(r==x)r--;
		if(son[l]+son[r]>lim) return 0;
		l++,r--;
	}
	return 1;
}
in int dfs(int u,int fa,int lim)
{
	int cnt=0;
	for(re int i=h[u];i;i=e[i].ne) {
		int v=e[i].to;cnt++;
		if(v==fa) continue;
		if(!dfs(v,u,lim))return 0;
		if(f[v]+1>lim) return 0;
	}
	if(cnt==1 && u!=1) {f[u]=0;return 1;}
	cnt=0;
	for(re int i=h[u];i;i=e[i].ne) {
		int v=e[i].to;
		if(v==fa)continue;
		son[++cnt]=f[v]+1;
	}
	sort(son+1,son+cnt+1);
	if(cnt%2==0) { //偶数个儿子
		int flag=0;
		for(re int i=1;i<=cnt/2;i++)
			if(son[i]+son[cnt-i+1]>lim) flag=1; 
		if(!flag) { f[u]=0;return 1;};//都合法
		if(u==1) {return 0;}
		int l=1,r=cnt-1,ans=-1;
		while(l<=r) {
			int mid=l+r>>1;
			if(son[mid]>lim) {l=mid+1;continue;}
			if(check1(1,cnt-1,mid,lim)) r=mid-1,ans=mid;
			else l=mid+1;
		} //二分出哪个向上连接
		if(ans==-1) return 0;
		f[u]=son[ans];return 1;
	}
	else { //奇数个儿子
		if(cnt==1) {f[u]=son[1];return son[1]<=lim;}
		int l=1,r=cnt,ans=-1;
		while(l<=r) {
			int mid=l+r>>1;
			if(son[mid]>lim) {l=mid+1;continue;}
			if(check1(1,cnt,mid,lim)) r=mid-1,ans=mid;
			else l=mid+1;
		}
		if(ans==-1) return 0;
		f[u]=son[ans];return 1;
	}
		
}
in int check(int mid)
{
	return dfs(1,0,mid);
}
int main()
{
	n=read();
	for(re int i=1;i<n;i++) {
		int x=read(),y=read();
		add(x,y),add(y,x),c[x]++,c[y]++;
	}
	int ans=0;
	for(re int i=2;i<=n;i++) ans+=(c[i]-1)/2;
	ans+=(c[1]+1)/2;
	cout<<ans<<' ';
	int l=0,r=n-1;ans=0;
	while(l<=r)
	{
		int mid=l+r>>1;
		if(check(mid))
			if(f[1]<=mid)r=mid-1,ans=mid;
			else l=mid+1;
		else
			l=mid+1;
	}
	cout<<ans<<endl;
	return 0;
}

posted @ 2019-10-25 15:32  yzhx  阅读(131)  评论(2编辑  收藏  举报