【BZOJ2067】[Poi2004]SZN 二分+树上贪心

【BZOJ2067】[Poi2004]SZN

Description

String-Toys joint-stock 公司需要你帮他们解决一个问题. 他们想制造一个没有环的连通图模型. 每个图都是由一些顶点和特定数量的边构成. 每个顶点都可以连向许多的其他顶点.一个图是连通且无环的. 图是由许多的线做成的.一条线是一条连接图中两个顶点之间的路径.由于一些技术原因,两条线之间不能有重叠的部分,要保证图中任意一条边都被且仅被一条线所覆盖.由于一些技术原因,做一个这样的图的模型的费用取决于用了多少条线以及最长的那条的长度. (每条边的长度都为1.),给出对应的图,求出最少能用多少条线以及在用最少线的情况下最长的那根线最短可以为多少.

Input

第一行仅包含一个数n – 顶点的总数, 2 <= n <= 10 000. 顶点从1 到 n进行编号. 接下来的n - 1 行描述这些边, 每行两个数a 和 b, 1 <= a, b <= n, a <> b. 表示顶点a和顶点b之间有一条边.

Output

输出两个数,最少用多少条线以及在用最少线的情况下最长线最短可以为多少.

Sample Input

9
7 8
4 5
5 6
1 2
3 2
9 8
2 5
5 8

Sample Output

4 2

HINT

题解:第一问结论好重要啊。

设一个点的度数为d[i],我们统计多少条路径的最高点是这个点。对于非根的点,它肯定有一条路是从父亲那里来的,这条路还能延伸至它的一个儿子。那么对于剩下的儿子,将他们两两配对,剩余的单算,所以这个点对答案的贡献就是$lceilfrac {d[i]-2} 2 ceil = lfloor frac {d[i]-1} 2 floor$。对于根节点,它的贡献是$lceilfrac {d[1]} 2 ceil$,所以$ans=1+sumlimits_{i=1}^nlfloor frac {d[i]-1} 2 floor$。

第二问的思路与第一问类似,容易想到先二分答案limit,然后令f[i]表示i到父亲那条路径的最短长度是多少。那么f[i]怎么求呢?我们将i的所有儿子的f值拿出来排序,发现i的f值可以二分得到。假如f[i]=f[j]+1,那么我们将j去掉,其余的f值从不断取出最大的和最小的两两配对,如果所有配对后的路径长度都<=limit,则这是一个合法的f值,继续二分即可。

注意:如果一个点的儿子个数是奇数,那么我们需要再添一个f值为0的儿子。根节点的f值需要特殊处理。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=10010;
int n,m,ans,cnt,lim,flag;
int to[maxn<<1],next[maxn<<1],head[maxn],d[maxn];
int g[maxn],f[maxn];
inline void add(int a,int b)
{
	to[cnt]=b,next[cnt]=head[a],head[a]=cnt++;
}
bool check(int x)
{
	int i,j;
	for(i=1,j=m;i<j;i++,j--)
	{
		if(i==x)	i++;
		if(j==x)	j--;
		if(g[i]+g[j]>lim)	return 0;
	}
	return 1;
}
void dfs(int x,int fa)
{
	int i;
	for(i=head[x];i!=-1;i=next[i])	if(to[i]!=fa)	dfs(to[i],x);
	for(m=0,i=head[x];i!=-1;i=next[i])	if(to[i]!=fa)	g[++m]=f[to[i]]+1;
	if(x==1)
	{
		sort(g+1,g+m+1);
		flag&=check((m&1)?m:0);
		return ;
	}
	if(!(m&1))	g[++m]=0;
	sort(g+1,g+m+1);
	int l=1,r=m+1,mid;
	while(l<r)
	{
		mid=(l+r)>>1;
		if(g[mid]+1<=lim&&check(mid))	r=mid;
		else	l=mid+1;
	}
	if(r==m+1)	flag=0;
	f[x]=g[r];
}
inline int rd()
{
	int ret=0,f=1;	char gc=getchar();
	while(gc<'0'||gc>'9')	{if(gc=='-')	f=-f;	gc=getchar();}
	while(gc>='0'&&gc<='9')	ret=ret*10+(gc^'0'),gc=getchar();
	return ret*f;
}
int main()
{
	n=rd();
	int i,a,b,l=1,r=n;
	memset(head,-1,sizeof(head));
	for(i=1;i<n;i++)	a=rd(),b=rd(),add(a,b),add(b,a),d[a]++,d[b]++;
	for(ans=i=1;i<=n;i++)	ans+=(d[i]-1)>>1;
	while(l<r)
	{
		flag=1,lim=(l+r)>>1,dfs(1,0);
		if(flag)	r=lim;
		else	l=lim+1;
	}
	printf("%d %d",ans,r);
	return 0;
}
原文地址:https://www.cnblogs.com/CQzhangyu/p/7859175.html