【Codeforces 768G】The Winds of Winter
线段树合并
题目大意:
给定一棵有根树,去掉一个点及与其相关的边后,会变成一棵森林。
删掉结点后,可以选择一个结点,断掉其与父亲的连边,并连到另一个结点上。不能改变森林的树的个数。最多操作一次。
要使得森林中最大的树的大小最小。
对删掉每个结点,求森林中最大的树的大小最小是多少。询问互相独立。
题解:
对删掉每个点后,显然是删最大的树里的子树,接在最小的树上。
删掉每个结点后的最大、次大、最小子树都可以处理出来。
令最大、最小子树的大小分别为 $mx,mn$,显然让它们尽可能接近是最好的。
令 $mid=\lfloor\frac{mx+mn}{2}\rfloor$,这是我们要删到的目标状态。
我们要在最大的那棵树中删除 $k$。那么当 $mn+k\leq mid$ 时令 $k$ 尽可能大,当 $mn+k> mid$ 时令 $k$ 尽可能小。
我们在这棵子树中找满足条件的最优的 $k$ 即可,然后取较优答案。注意答案至少是次大的子树大小,要和它取 $\max$。
那么我们需要实现在一棵子树中,查找大小在 $l\sim r$ 之间的最大、最小子树大小。
由于是有根树,所以其儿子所在子树的信息,可以通过线段树合并统计,然后在线段树上二分即可查询。
考虑如何统计每个点父亲所在信息。
设当前结点的子树大小为 $sz$,发现这个点到根路径上的子树大小都减少了 $sz$。
还有就是,以当前结点为根的这棵子树都没有了。
那我们分两部分查询:1. 整棵树去掉到根路径去掉当前子树。2. 到根路径。
对于第一种情况,我们用线段树维护到根路径所有点的子树大小信息,还有通过线段树合并得到的当前子树的信息和整棵树的信息。这些信息是可以直接在线段树上做差的。在三棵树上一起二分即可。
对于第二种情况,我们维护了到根路径的信息,然后相当于是需要进行全局减某个数以后再进行查询。这个可以把查询的范围 $l,r$ 都加上 $sz$ 后查询,然后查询的结果再减去 $sz$ 就好了。
时间复杂度 $O(n\log n)$,空间复杂度 $O(n\log n)$。
这里对线段树合并进行可持久化一下比较好处理。
一开始以为可持久化线段树合并的空间复杂度是两个 $\log$ 的,实际上只多了一倍常数。
Code:
#include<iostream>
#include<algorithm>
using namespace std;
const int inf=0x3fffffff;
const int N=1e5+5;
int n,rt,head[N],sz[N],fa[N],ans[N],deg[N],cnt,sr;
struct info{
int mx1,id1,mx2,id2,mn,idn;
inline void update(int id,int sz){
if(sz<mn||mn==0)mn=sz,idn=id;
if(sz>mx1){
mx2=mx1,id2=id1;
mx1=sz,id1=id;
}else
if(sz>mx2){
mx2=sz,id2=id;
}
}
}f[N];
struct edge{
int to,nxt;
}e[N<<1];
namespace sgt{
const int M=N*380;
int rt[N];
int ls[M],rs[M],sz[M],nod;
void add(int&o,int l,int r,const int&pos){
if(!o)o=++nod;
if(l==r)++sz[o];else{
const int mid=l+r>>1;
if(pos<=mid)add(ls[o],l,mid,pos);else add(rs[o],mid+1,r,pos);
sz[o]=sz[ls[o]]+sz[rs[o]];
}
}
void del(int&o,int l,int r,const int&pos){
if(l==r)--sz[o];else{
const int mid=l+r>>1;
if(pos<=mid)del(ls[o],l,mid,pos);else del(rs[o],mid+1,r,pos);
sz[o]=sz[ls[o]]+sz[rs[o]];
}
}
int merge(int ld,int rd,int l,int r){
if(!ld||!rd)return ld|rd;
int id=++nod;
if(l==r){
sz[id]=sz[ld]+sz[rd];
return id;
}
const int mid=l+r>>1;
ls[id]=merge(ls[ld],ls[rd],l,mid);
rs[id]=merge(rs[ld],rs[rd],mid+1,r);
sz[id]=sz[ls[id]]+sz[rs[id]];
return id;
}
int query_min(int rd,int ld,int ld_,int l,int r,const int&L,const int&R){
if(sz[rd]<=sz[ld]+sz[ld_]||L>R)return inf;
if(l==r)return l;
const int mid=l+r>>1;
int ret=inf;
if(L<=mid)ret=query_min(ls[rd],ls[ld],ls[ld_],l,mid,L,R);
if(ret!=inf)return ret;
if(mid<R)ret=query_min(rs[rd],rs[ld],rs[ld_],mid+1,r,L,R);
return ret;
}
int query_max(int rd,int ld,int ld_,int l,int r,const int&L,const int&R){
if(sz[rd]<=sz[ld]+sz[ld_]||L>R)return 0;
if(l==r)return l;
const int mid=l+r>>1;
int ret=0;
if(mid<R)ret=query_max(rs[rd],rs[ld],rs[ld_],mid+1,r,L,R);
if(ret!=0)return ret;
if(L<=mid)ret=query_max(ls[rd],ls[ld],ls[ld_],l,mid,L,R);
return ret;
}
}
void dfs(int now,int pre){
sz[now]=1;
for(int i=head[now];i;i=e[i].nxt)
if(e[i].to!=pre){
fa[e[i].to]=now;
dfs(e[i].to,now);
sz[now]+=sz[e[i].to];
f[now].update(e[i].to,sz[e[i].to]);
}
if(now!=rt)f[now].update(fa[now],n-sz[now]);
sgt::add(sgt::rt[now],1,n,sz[now]);
for(int i=head[now];i;i=e[i].nxt)
if(e[i].to!=pre)
sgt::rt[now]=sgt::merge(sgt::rt[now],sgt::rt[e[i].to],1,n);
}
int query_min(int l,int r,int nw,int qrt){
if(l>r)return 0x3fffffff;
if(fa[nw]!=qrt)
return sgt::query_min(sgt::rt[qrt],0,0,1,n,l,r);
return min(sgt::query_min(sgt::rt[rt],sgt::rt[nw],sr,1,n,l,r),
sgt::query_min(sr,0,0,1,n,min(l+sz[nw],n+1),min(r+sz[nw],n))-sz[nw]);
}
int query_max(int l,int r,int nw,int qrt){
if(l>r)return 0;
if(fa[nw]!=qrt)
return sgt::query_max(sgt::rt[qrt],0,0,1,n,l,r);
return max(sgt::query_max(sgt::rt[rt],sgt::rt[nw],sr,1,n,l,r),
sgt::query_max(sr,0,0,1,n,min(l+sz[nw],n+1),min(r+sz[nw],n))-sz[nw]);
}
void getans(int num){
if(deg[num]==1)ans[num]=n-1;else{
const int mid=f[num].mn+f[num].mx1>>1;
int mn=query_max(1,mid-f[num].mn,num,f[num].id1);
int mx=query_min(mid+1-f[num].mn,f[num].mx1-1,num,f[num].id1);
int s=f[num].mx1;
if(mn>0)s=min(s,max(f[num].mx1-mn,f[num].mx2));
if(mx<f[num].mx1)s=min(s,max(f[num].mn+mx,f[num].mx2));
ans[num]=s;
}
}
void get(int now,int pre){
getans(now);
sgt::add(sr,1,n,sz[now]);
for(int i=head[now];i;i=e[i].nxt)
if(e[i].to!=pre)
get(e[i].to,now);
sgt::del(sr,1,n,sz[now]);
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;++i){
int u,v;
cin>>u>>v;
if(!u||!v)rt=u|v;else{
e[++cnt]=(edge){v,head[u]},head[u]=cnt;
e[++cnt]=(edge){u,head[v]},head[v]=cnt;
++deg[u],++deg[v];
}
}
dfs(rt,0);
getans(rt);
for(int i=head[rt];i;i=e[i].nxt)
get(e[i].to,rt);
for(int i=1;i<=n;++i)
cout<<ans[i]<<'\n';
return 0;
}