[CF1039D]You Are Given a Tree【根号分治】【贪心】

>Link

luogu CF1039D


>Description

给定一棵 n 个点的树,点的编号为 1∼n。对于一个正整数 k,你需要不断进行以下操作:

选择树上一个长度为 k 的路径,要求路径上每一个点都没有被覆盖,然后把这个路径上所有点覆盖掉。
我们定义一条路径的长度为这条路径上点的数量。

定义 f(k) 表示能操作的最大次数。对于 k∈[1,n],分别计算 f(k) 的值。

n ≤ 1 0 5 n\le10^5 n105


>解题思路

考虑部分分,对每一个 k k k 可以如何求解 f ( k ) f(k) f(k)
因为一个点最多在一条链上,也就是一个点最多给答案贡献“1”,所以我们不可能“子树减少一点贡献,在 x 这里增加贡献,让答案更大(题解说的太好了,拷一下qwq)”,因为如果让子树减少最少情况下的贡献1,当前节点也最多只能贡献1,这样答案显然不会更优。也就是说当前这个节点如果要放进一条长为 k k k 的链,只能用它的子树里其他儿子没用到的位置
所以我们可以 O ( n ) O(n) O(n) 求出 f ( k ) f(k) f(k)

按照上面的做法, O ( n 2 ) O(n^2) O(n2)也还是过不了
我们发现对于 k ∈ [ 1 , n ] k\in [1,n] k[1,n] f ( k ) f(k) f(k) 是单调不升的!也就是, f ( k ) f(k) f(k) 是呈一个阶梯状下降,最多一共有 1 ∼ n 1 \sim n 1n 种答案
然后就可以对这 n n n 种答案每次跑个二分,求当前这个阶梯最右端是哪里,判断就用上面的方法 O ( n ) O(n) O(n) 判断
但是这样是 O ( n 2 l o g n ) O(n^2logn) O(n2logn),依然过不了

正解就要用到 根号分治
我们把 n n n 个询问拆成两部分,前半部分跑贪心(暴力),后半部分跑二分
假设在 T T T 处分割,后半部分答案的范围就为 1 ∼ n / T 1\sim n/T 1n/T,那前半部分的时间复杂度为 O ( n T ) O(nT) O(nT),后半部分为 O ( n 2 l o g n / T ) O(n^2logn/T) O(n2logn/T)
解出来两部分相等的话(总的时间复杂度趋于最小), T = n l o g n T=\sqrt{nlogn} T=nlogn ,时间复杂度 O ( n n l o g n ) O(n\sqrt{nlogn}) O(nnlogn )

最后wyc大爷说用邻接表深搜会很慢过不了,因为要先处理儿子再处理父亲,所以我就按bfs序从大到小处理


>代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#define N 100010
using namespace std;

queue<int> Q;
struct edge
{
    
    
	int to, nxt;
} e[N * 2];
int n, h[N], cnt, f[N], maxn[N], maxnn[N], bfn[N], fa[N], tot;
int T, ans[N];

void add (int u, int v)
{
    
    
	e[++cnt] = (edge){
    
    v, h[u]}; h[u] = cnt;
	e[++cnt] = (edge){
    
    u, h[v]}; h[v] = cnt;
}
void bfs ()
{
    
    
	bfn[1] = ++tot;
	Q.push (1);
	while (!Q.empty())
	{
    
    
		int u = Q.front();
		Q.pop();
		for (int i = h[u]; i; i = e[i].nxt)
		{
    
    
			int v = e[i].to;
			if (bfn[v]) continue;
			bfn[v] = ++tot;
			fa[bfn[v]] = bfn[u];
			Q.push (v);
		}
	}
}
int solve (int k)
{
    
    
	if (ans[k] != -1) return ans[k];
	for (int i = 1; i <= n; i++)
	  f[i] = maxn[i] = maxnn[i] = 0;
	int m;
	for (int i = n; i >= 1; i--)
	{
    
    
		if (maxn[i] + maxnn[i] + 1 >= k) f[i]++;
		else
		{
    
    
			m = maxn[i] + 1;
			if (m > maxn[fa[i]])
			  maxnn[fa[i]] = maxn[fa[i]], maxn[fa[i]] = m;
			else if (m > maxnn[fa[i]]) maxnn[fa[i]] = m;
		}
		f[fa[i]] += f[i];
	}
	return f[1];
}
int find (int l, int r)
{
    
    
	int mid, ret = l, x = ans[l] = solve (l);
	while (l <= r)
	{
    
    
		mid = (l + r) / 2;
		if ((ans[mid] = solve (mid)) == x) ret = max (ret, mid);
		if (ans[mid] >= x) l = mid + 1;
		else r = mid - 1;
	}
	return ret;
}

int main()
{
    
    
	int u, v;
	scanf ("%d", &n);
	for (int i = 1; i <= n; i++) ans[i] = -1;
	for (int i = 1; i < n; i++)
	{
    
    
		scanf ("%d%d", &u, &v);
		add (u, v);
	}
	bfs ();
	T = sqrt (log2 ((double)n) * (double)n);
	for (int i = 1; i <= T; i++)
	  ans[i] = solve (i);
	int now = T + 1, pos;
	for (int i = 1; i <= n / T; i++)
	{
    
    
		pos = find (now, n);
		now = pos + 1;
		if (now > n) break;
	}
	for (int i = 1; i <= n; i++)
	{
    
    
		if (ans[i] == -1) ans[i] = ans[i - 1];
		printf ("%d\n", ans[i]);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_43010386/article/details/121174009