1. 程式人生 > 其它 >CF1039D-You Are Given a Tree【根號分治,貪心】

CF1039D-You Are Given a Tree【根號分治,貪心】

正題

題目連結:https://www.luogu.com.cn/problem/CF1039D


題目大意

給出\(n\)個點的一棵樹,然後對於\(k\in[1,n]\)求每次使用一條長度為\(k\)的鏈覆蓋樹並且不能重複覆蓋點時最大覆蓋條數。

\(1\leq n\leq 10^5\)


解題思路

先考慮暴力怎麼做,因為每條鏈的價值都是一,顯然的一種貪心思想是能合併的就合併(沒有讓出一條鏈給另一條鏈騰空間的必要)。

這樣的複雜度是\(O(n)\)的,但是對於每個都要求所以需要優化。

之後考慮上根號分治,對於一個\(k\)的答案顯然不會超過\(\frac{n}{k}\),所以可以當\(k\leq \sqrt n\)

的時候暴力做,然後由於答案遞增,大於\(\sqrt n\)\(k\)答案的取值不會超過\(\sqrt n\),每次二分斷點即可。時間複雜度\(O(n\sqrt n\log n)\)

其實發現這樣還是不夠快,可以找到一個更好的閾值,設為\(T\),那麼前面的複雜度就是\(T\),後面的複雜度就是\(\frac{n}{T}\log n\),用平衡規劃的思想當\(T=\frac{n}{T}\log n\)時最快,也就是\(T=\sqrt{n\log n}\)時最快了。


code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=1e5+10;
struct node{
	int to,next;
}a[N<<1];
int n,tot,cnt,dfn[N],ls[N],fa[N],f[N];
void addl(int x,int y){
	a[++tot].to=y;
	a[tot].next=ls[x];
	ls[x]=tot;return;
}
void dfs(int x){
	dfn[++cnt]=x;
	for(int i=ls[x];i;i=a[i].next){
		int y=a[i].to;
		if(y==fa[x])continue;
		fa[y]=x;dfs(y);
	}
	return;
}
int solve(int k){
	if(k==1)return n;
	int ans=0;
	for(int i=1;i<=n;i++)f[i]=1;
	for(int i=n;i>=1;i--){
		int x=dfn[i];
		if(f[x]&&f[fa[x]]){
			if(f[x]+f[fa[x]]>=k)
				ans++,f[fa[x]]=0;
			else f[fa[x]]=max(f[fa[x]],f[x]+1);
		}
	}
	return ans;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<n;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		addl(x,y);addl(y,x);
	}
	dfs(1);
	int T=sqrt((double)n*(log(n)/log(2))),last,z=T+1;
	for(int i=1;i<=T;i++)printf("%d\n",last=solve(i));
	while(z<=n){
		int l=z+1,r=n,k=solve(z);
		while(l<=r){
			int mid=(l+r)>>1;
			if(solve(mid)<k)r=mid-1;
			else l=mid+1;
		}
		for(int i=z;i<=r;i++)
			printf("%d\n",k);
		z=r+1;
	}
	return 0;
}