CF1039D You Are Given a Tree
咋其他题解都带根号?根号是坏文明,这里是俩 \(\log\) 做法,能够跑到 \(300\) ms 以内。
首先考虑暴力贪心,从叶子向根合并,可以取出一条链的时候就取出来,否则就连一条尽可能长的链往上合并。
具体的设 \(f_{x,i}\) 为当 \(k=i\) 时,在 \(x\) 处能取出的最长链。那么也就是从 \(f_{y,i},y\in son(x)\) 中选出最大值和次大值,如果连起来长度 \(\geq i\),那么答案加一,将 \(f_{x,i}\) 置为 \(0\);否则 \(f_{x,i}\) 等于儿子中最大值加一。这东西肯定是 \(O(n^2)\) 的,考虑优化。
记 \(siz_x\) 是 \(x\) 及其子树大小,\(d_x\) 为 \(x\) 子树中最深点与 \(x\) 的距离加一。
有一个比较常见的 trick 是树剖,然后直接继承重儿子的 \(f\) 值,从这点上看这个做法可能类似 dsu on tree。
那么现在操作变成了,继承时对全局加一,然后在轻儿子中找到最大值和次大值,进行更新。
首先不难发现,子树中选出一条最长链的长度是不超过子树大小的,换句话来说,当 \(i>siz_x\),\(f_{x,i}=d_x\)。
那么也就意味着,我们记 \(MS(x)\) 为 \(x\) 的轻儿子中 \(siz\) 最大值,那么对所有 \(i>MS(x)\) 的更新都是相同的。
然后我们发现,经典结论所有轻儿子的 \(siz\) 和是 \(O(n\log n)\) 级别的;所有长度的答案和是调和级数,是 \(O(n\ln n)\) 级别的。
如果我们可以对 \(i>MS(x)\) 的修改打包,每次只暴力更新 \(i\leq MS(x)\) 的位置和所有可以贡献到答案的位置,时间复杂度就是对的。
而由于我们需要找出一段区间中所有满足一定条件的位置,所以考虑线段树。
首先一条链的线段树的大小是 \(siz_{top}\) 的(\(top\) 表示这条链的链头),这个东西的和自然也是 \(O(n\log n)\) 级别的。
我们可以在线段树上维护区间 \(f_{x,i}-i\) 最大值,具体的怎么判断是否可以贡献到答案就是三条链里取两条长的,不多说了。
\(i\leq MS(x)\) 的部分就直接先暴力把所有轻儿子的线段树上维护的值取出来,然后预处理取完 \(\max\) 后再暴力递归到当前线段树的叶子节点修改。
\(i> MS(x)\) 打包修改的部分就相当于如果区间中有可以贡献到答案的位置就继续递归,否则打一个区间加、区间取 \(\max\) 的 tag,值得注意的是我们先前维护的 \(f_{x,i}-i\) 最大值可能可以被区间取的 \(\max\) 影响,比方说区间 \(\max\) 是 \(s\) 的话,那个最大值是要和 \(s-l\)(\(l\) 是区间的左端点)取 \(\max\) 的。
那么总时间复杂度就是 \(O(n\log^2 n)\) 的,实际上你修改都是一起修改的,所以大概常数还很小,薄纱一众根号做法。
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#define ll long long
using namespace std;
const int MAXN=1e5+10;
int n,d[MAXN],siz[MAXN],h[MAXN],top[MAXN],ans[MAXN];
vector <int> v[MAXN];typedef pair<int,int> P;P f[MAXN];
inline void max(P &x,int y)
{
if(y>x.first) swap(y,x.first);
if(y>x.second) x.second=y;
}
inline void max(P &x,P y)
{max(x,y.first),max(x,y.second);}
namespace segment_tree
{
#define LS t[t[p].ls]
#define RS t[t[p].rs]
int R[MAXN],tot;queue <int> q;
struct node{int num,add,s,ls,rs,l;}t[MAXN<<5];
inline int N()
{
if(q.empty()) return ++tot;
int x=q.front();q.pop();return x;
}
inline void add(int p,int c,int z)
{
t[p].add+=c,t[p].s=max(t[p].s+c,z);
t[p].num=max(t[p].num+c,t[p].s-t[p].l);
}
inline void push_up(int p)
{t[p].num=max(LS.num,RS.num);}
inline void push_down(int p)
{
add(t[p].ls,t[p].add,t[p].s);
add(t[p].rs,t[p].add,t[p].s);
t[p].add=t[p].s=0;return ;
}
void build(int l,int r,int p)
{
t[p].l=l;if(l==r){t[p].num=-l+1;return ;}
int mid=(l+r)>>1;
build(l,mid,t[p].ls=N());
build(mid+1,r,t[p].rs=N());
push_up(p);return ;
}
void get(int l,int r,int p)
{
if(l==r)
{
max(f[l],t[p].num+l);
t[p]={},q.push(p);return ;
}
int mid=(l+r)>>1;push_down(p);
if(t[p].ls) get(l,mid,t[p].ls);
if(t[p].rs) get(mid+1,r,t[p].rs);
t[p]={},q.push(p);return ;
}
void upd(int l,int r,int p,int x,int S,int D)
{
if(l>x&&S<l&&t[p].num+D+1<0)
{add(p,1,D+1);return ;}
if(l==r)
{
t[p].s=0;if(l<=x)
{
int S=f[l].first+max(f[l].second,t[p].num+l)+1;
if(S>=l) ++ans[l],t[p].num=-l;
else t[p].num=max(t[p].num,f[l].first-l)+1;
}
else ++ans[l],t[p].num=-l;return ;
}
int mid=(l+r)>>1;push_down(p);
upd(l,mid,t[p].ls,x,S,D);
upd(mid+1,r,t[p].rs,x,S,D);
push_up(p);return ;
}
};using namespace segment_tree;
void dfs(int x,int fa=0)
{
P D={0,0};siz[x]=1;
for(int y:v[x])
{
if(y==fa) continue;
dfs(y,x),max(D,d[y]),siz[x]+=siz[y];
if(siz[y]>siz[h[x]]) h[x]=y;
}
d[x]=D.first+1;
}
void calc(int x,int fa=0)
{
if(!h[x])
{
if(siz[top[x]]>=2)
build(2,siz[top[x]],R[x]=N());
return ;
}
top[h[x]]=top[x],calc(h[x],x),R[x]=R[h[x]];
for(int y:v[x])
if(y!=fa&&y!=h[x]) top[y]=y,calc(y,x);
P MD={0,0};int MS=0;
for(int y:v[x])
{
if(y==fa||y==h[x]) continue;
max(MD,d[y]),MS=max(MS,siz[y]);
max(f[siz[y]+1],d[y]);
}
for(int i=2;i<=MS;++i) max(f[i],f[i-1]);
for(int y:v[x])
if(y!=fa&&y!=h[x]&&siz[y]>=2) get(2,siz[y],R[y]);
int D=MD.first,S=MD.first+MD.second+1;
upd(2,siz[top[x]],R[x],MS,S,D);
for(int i=2;i<=MS+1;++i) f[i]={0,0};return ;
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.in","r",stdin);
freopen("out.out","w",stdout);
#endif
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
cin>>n;for(int i=1,x,y;i<n;++i)
cin>>x>>y,v[x].push_back(y),v[y].push_back(x);
dfs(1),top[1]=1,calc(1),ans[1]=n;
for(int i=1;i<=n;++i) cout<<ans[i]<<'\n';
return 0;
}