1. 程式人生 > >CF 1042F

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\n",cct+1);
	return 0;
}