这个题好像贪心做法就不太会。/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;
}