CF1039D You Are Given a Tree

这个题好像贪心做法就不太会。/wq
果然是因为自己太菜了吗。
因为这是一个根号分治的题目,不过好像也可以整体二分,整体二分还在学,如果学了会回来再做一遍这个题。
先考虑写出一个贪心,考虑如果有能拼起来大于 \(k\) 的链,我们就拼起来,那么这样一次操作是 \(O(n)\)
如果我们对所有答案都进行操作的话,是一个 \(O(n ^ 2)\) 的复杂度。
我们考虑如果我们把 \(k <= T\) 的操作直接暴力做,那这是一个 \(O(nT)\) 的东西。
我们考虑对于 \(k\) 来说,他的答案是有单调性的,我们对 \(k >= T\) 考虑二分,二分到最右边那个答案一样的点,那这是一个\(O(\frac{n^2logn}{T})\)的东西
\(T = \sqrt(nlogn)\) 较优
二分之后往需要的那一边扩展是我的一个习惯,害怕有些地方二分会挂掉,毕竟不同的二分的话,和正确答案的差别不超过\(2\)左右,所以其实没有慢多少。

CF1039D You Are Given a Tree
#include<iostream>
#include<cstdio>
#include<cmath>
#define ll long long
#define N 100005

ll n;

ll head[N],cnt;

struct P{
	int to,next;
}e[N * 2];

inline void add(int x,int y){
	e[++cnt].to = y;
	e[cnt].next = head[x];
	head[x] = cnt;
}

ll fa[N],dfn[N],dfncnt;

inline void dfs(int u,int f){
	fa[u] = f;
	for(int i = head[u];i;i = e[i].next){
		int v = e[i].to;
		if(v == f)
		continue;
		dfs(v,u);
	}
	dfn[++dfncnt] = u;
}

ll f[N];//该点下最长有多少链 

inline ll solve(int k){
	ll ans = 0;
	for(int i = 1;i <= n;++i)
	f[i] = 1;
	for(int i = 1;i <= n;++i){
		int u = dfn[i];
		int fi = fa[u];
		if(fi && f[u] != -1 && f[fi] != -1)
		if(f[u] + f[fi] >= k)
		ans ++ ,f[fi] = -1;
		else
		f[fi] = std::max(f[fi],f[u] + 1);
	}
	return ans;
}

ll ans[N];

int main(){
	scanf("%lld",&n);
	for(int i = 1;i < n;++i){
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y);add(y,x);
	}
	dfs(1,0);
	ll s = std::sqrt(n * log2(n));
	ans[1] = n;//这里因为不用和父亲链,所以直接solve可能会出事。 
	for(int i = 2;i <= s;++i)
	ans[i] = solve(i);
	for(int i = s + 1;i <= n;++i){
		ans[i] = solve(i);
		int l = i,r = n,to = i;
		while(l <= r){
			ll mid = (l + r) >> 1;
			if(solve(mid) == ans[i])
			l = mid + 1,to = mid;
			else
			r = mid - 1;
//			std::cout<<l<<" "<<r<<std::endl;
		}
		to -= 1;//用一点时间换答案正确。 
		while(solve(to + 1) == ans[i] && to <= n)
		to ++ ;
		for(int j = i + 1;j <= to;++j)
		ans[j] = ans[i];
		i = to; 
	}
	for(int i = 1;i <= n;++i)
	std::cout<<ans[i]<<std::endl;
}
posted @ 2021-05-12 21:45  fhq_treap  阅读(83)  评论(0编辑  收藏  举报