BZOJ3572【HNOI2014】世界树题解(虚树+树上倍增+换根DP)

题目:BZOJ3572.
题目大意:给定一棵 n n n个点的树与一些询问,每次询问给定 k k k个点 a i a_i ai.现在定义一个点从属于距离它最近的 a i a_i ai,若距离相等则属于编号最小的 a i a_i ai,要求输出对于每个 a i a_i ai有多少点属于 a i a_i ai.
1 ≤ n , ∑ k ≤ 3 ∗ 1 0 5 1\leq n,\sum k\leq3*10^5 1n,k3105.

首先建出虚树,发现这题不在虚树上的信息并不是很好处理.

考虑先求出虚树上每个点属于哪个点,可以类比换根DP通过两遍dfs实现.

接下来考虑不在虚树上的点.先dfs整棵虚树,dfs点 k k k的时候我们算出整棵以 k k k为根的子树对答案的贡献.

先假设 k k k的整棵子树都属于 k k k所属的节点.之后对于 k k k的每个儿子 y y y,若 k k k所属的节点与 y y y所属节点是同一个,那么我们只去掉 y y y子树的贡献,在下面的dfs中重新计算 y y y的贡献.

否则的话,必然存在 y y y k k k路径上的一个分界点 z z z使得在这之下的都属于 y y y所属节点且在 z z z之上的都属于 k k k所属节点,现在的问题是如何找到 z z z.

显然,我们 k k k到其所属节点的距离、 y y y到其所属节点的距离与 k , y k,y k,y的距离就可以求出 z z z y y y的多少级祖先了,有了距离后用树上倍增就可以得到 z z z了.

然后这个问题就解决啦然而细节很多代码巨难写,时间复杂度 O ( ( n + ∑ k ) log ⁡ n ) O((n+\sum k)\log n) O((n+k)logn).

代码如下:

#include<bits/stdc++.h>
  using namespace std;
 
#define Abigail inline void
typedef long long LL;
 
const int N=300000,C=20,INF=(1<<30)-1;
 
int n,m;
struct side{
  int y,next;
}e[N*4+9];
int lin[2][N+9],cs;
 
void Ins(int id,int x,int y){e[++cs].y=y;e[cs].next=lin[id][x];lin[id][x]=cs;}
 
int dep[N+9],siz[N+9],gr[N+9][C];
int dfs[N+9],co;
 
void Dfs_doubly(int k,int fa){
  gr[k][0]=fa;
  dep[k]=dep[fa]+1;
  siz[k]=1;
  dfs[k]=++co;
  for (int i=1;i<C;++i)
    gr[k][i]=gr[gr[k][i-1]][i-1];
  for (int i=lin[0][k];i;i=e[i].next)
    if (e[i].y^fa){
      Dfs_doubly(e[i].y,k);
      siz[k]+=siz[e[i].y];
    }
}
 
int Query_lca(int x,int y){
  if (dep[x]<dep[y]) swap(x,y);
  for (int i=C-1;i>=0;--i)
    if (dep[gr[x][i]]>=dep[y]) x=gr[x][i];
  if (x==y) return x;
  for (int i=C-1;i>=0;--i)
    if (gr[x][i]^gr[y][i]) x=gr[x][i],y=gr[y][i];
  return gr[x][0];
}
 
int Query_anc(int x,int v){
  for (int i=C-1;i>=0;--i)
    if (v>=1<<i) x=gr[x][i],v-=1<<i;
  return x;
}
 
int Query_dis(int x,int y){return dep[x]+dep[y]-2*dep[Query_lca(x,y)];}
 
int ca,a[N+9],ta[N+9],lca[N+9],tag[N+9];
int st[N+9],cst;
 
bool cmp(const int &a,const int &b){return dfs[a]<dfs[b];}
 
void Build(){
  for (int i=1;i<=ca;++i) ta[i]=a[i];
  sort(ta+1,ta+ca+1,cmp);
  st[cst=1]=ta[0]=1;
  for (int i=1;i<=ca;++i){
    lca[i]=Query_lca(ta[i],ta[i-1]);
    lin[1][lca[i]]=tag[lca[i]]=0;
  }
  for (int i=1;i<=ca;++i) lin[1][ta[i]]=0,tag[ta[i]]=1;
  for (int i=1;i<=ca;++i){
    for (;cst>1&&dep[lca[i]]<dep[st[cst-1]];--cst)
      Ins(1,st[cst-1],st[cst]);
    if (st[cst]^lca[i]){
      Ins(1,lca[i],st[cst]);
      if (st[--cst]^lca[i]) st[++cst]=lca[i];
    }
    if (st[cst]^ta[i]) st[++cst]=ta[i];
  }
  for (;cst>1;--cst) Ins(1,st[cst-1],st[cst]);
}
 
int bel[N+9],md[N+9];
 
void Dfs_bel1(int k){
  bel[k]=md[k]=INF;
  if (tag[k]) bel[k]=k,md[k]=0;
  for (int i=lin[1][k];i;i=e[i].next){
    Dfs_bel1(e[i].y);
    int t=Query_dis(k,bel[e[i].y]);
    if (t<md[k]||t==md[k]&&bel[e[i].y]<bel[k]) bel[k]=bel[e[i].y],md[k]=t;
  }
}

void Dfs_bel2(int k){
  for (int i=lin[1][k];i;i=e[i].next){
    int t=Query_dis(e[i].y,bel[k]);
    if (t<md[e[i].y]||t==md[e[i].y]&&bel[k]<bel[e[i].y]) bel[e[i].y]=bel[k],md[e[i].y]=t;
    Dfs_bel2(e[i].y);
  }
}
 
int ans[N+9];
 
void Dfs_ans(int k){
  ans[bel[k]]+=siz[k];
  for (int i=lin[1][k];i;i=e[i].next){
    if (bel[k]==bel[e[i].y]) ans[bel[k]]-=siz[e[i].y];
    else{
      int t=Query_dis(k,e[i].y)-1+md[k]-md[e[i].y]>>1;
      if (Query_dis(bel[k],bel[e[i].y])&1^1&&bel[e[i].y]<bel[k]) ++t;
      t=Query_anc(e[i].y,t); 
      ans[bel[k]]-=siz[t];
      ans[bel[e[i].y]]+=siz[t]-siz[e[i].y];
    }
    Dfs_ans(e[i].y);
  }
}
 
Abigail into(){
  scanf("%d",&n);
  for (int i=1;i<n;++i){
    int x,y;
    scanf("%d%d",&x,&y);
    Ins(0,x,y);Ins(0,y,x); 
  }
}
 
Abigail work(){
  Dfs_doubly(1,0);
}
 
Abigail getans(){
  scanf("%d",&m);
  for (int i=1;i<=m;++i){
    scanf("%d",&ca);
    for (int j=1;j<=ca;++j)
      scanf("%d",&a[j]);
    Build();
    Dfs_bel1(1);
    Dfs_bel2(1);
    for (int i=1;i<=ca;++i) ans[a[i]]=0;
    Dfs_ans(1);
    for (int i=1;i<=ca;++i)
      printf("%d ",ans[a[i]]);
    puts("");
  }
}
 
int main(){
  into();
  work();
  getans();
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值