>Link
luogu CF1039D
>Description
给定一棵 n 个点的树,点的编号为 1∼n。对于一个正整数 k,你需要不断进行以下操作:
选择树上一个长度为 k 的路径,要求路径上每一个点都没有被覆盖,然后把这个路径上所有点覆盖掉。
我们定义一条路径的长度为这条路径上点的数量。
定义 f(k) 表示能操作的最大次数。对于 k∈[1,n],分别计算 f(k) 的值。
n ≤ 1 0 5 n\le10^5 n≤105
>解题思路
考虑部分分,对每一个 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 1∼n 种答案
然后就可以对这 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 1∼n/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;
}