CF 1042F

玄学贪心...

题意:给出一棵树,要求将他的所有叶节点分成最少的组,且在每组中的任意两节点之间的距离不大于k

解析:

显然是个贪心啦...

稍微考虑一下贪心思想:

我们从下向上合并整棵树,在合并到某个节点时,我们把他以下的所有叶节点到他的距离全处理出来然后排序,设所有距离排序后为d1,d2...dn

接下来,我们从大到小枚举每个d,如果满足di+di-1+2<=k,那么说明从1到i的所有子节点都可以分到一组里面,而剩下的节点只能单独一个分进一组里了

然后我们向上返回不单独分组的点中距离最大的一个的距离,向上回溯合并即可

画图理解一下:

如图所示,这是一棵树,设k=2

首先我们合并5,6两个点,那么发现这两个点到根的距离都是1,于是我们开心地将这两个点合并到一个集合里,然后回溯

其余最底层节点同理

这是合并前叶节点的状态

 这是合并上去第一层的状态

继续合并一层:

于是我们可以继续开心地合并,这时可以发现,所有的次二层节点都无法再合并,这时合并结束,一共需要三个集合

可能这一点不是很好理解,所以我们再假设k=4,那么就可以把2和4合并起来,而3还是无法合并,这样就只需要两个集合了。

总结一下思路:将一个根节点以下的所有叶节点到根节点的距离排序,然后倒序枚举,对于所有的di+di-1+2>=k的部分,我们都要把di单独分组,而剩余的部分可以分进一组,将这一组中的叶节点到这个根节点最长的距离回溯到上一层即可。

证明贪心的正确性:

首先,剩余部分可以分成一组是显而易见的,因为不会有比这更优的方案

而其他的点需要单独分组的原因:假设这些点可以与另一部分子树中的某一部分合并成一个集合,那么根据距离的关系可知,剩下的分成一组的点一定也可以和那一部分合并成一个集合,但这两者却不能合为一个集合,所以无论怎么合并都至少需要两个集合,也就是这样分组一定是某一种最优方案!

这样就完事了

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#include <vector>
using namespace std;
struct Edge
{
	int next;
	int to;
}edge[2000005];
int head[1000005];
int inr[1000005];
int cnt=1;
int n,k;
void init()
{
	memset(head,-1,sizeof(head));
	cnt=1;
}
void add(int l,int r)
{
	edge[cnt].next=head[l];
	edge[cnt].to=r;
	head[l]=cnt++;
}
int cct=0;
int dfs(int x,int fx)
{
	vector<int>v;
	for(int i=head[x];i!=-1;i=edge[i].next)
	{
		int to=edge[i].to;
		if(to==fx)
		{
			continue;
		}
		v.push_back(dfs(to,x)+1);
	}
	if(v.size()==0)
	{
		return 0;
	}
	sort(v.begin(),v.end());
	int i;
	for(i=v.size()-1;i>=1;i--)
	{
		if(v[i]+v[i-1]<=k)
		{
			break;
		}
		cct++;
	}
	return v[i];
}
int main()
{
	scanf("%d%d",&n,&k);
	init();
	for(int i=1;i<n;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
		inr[x]++;
		inr[y]++;
	}
	int rot=0;
	for(int i=1;i<=n;i++)
	{		
		if(!rot&&inr[i]!=1)
		{
			rot=i;
			break;
		}
	}
	dfs(rot,rot);
	printf("%d
",cct+1);
	return 0;
}
原文地址:https://www.cnblogs.com/zhangleo/p/10764174.html