[ SPOJ PT07J ] Query on a tree III

\(\\\)

Description


其实这题才是正版的 Qtree3......

给定 \(n\) 个点,以 \(1\) 号节点为根的树,点有点权。

\(m\) 次询问 以 \(x\) 为根的子树内,点权第 \(k\) 小的 节点编号 是多少。

有多组测试数据,每组数据以 \(DONE\) 结尾。

  • \(n,m\le 10^5\)

\(\\\)

Solution


注意到一棵树的子树 \(DFS\) 序是连续的。

一遍 \(DFS\) 确定 \(DFS\) 序以及各个点的子树大小。

\(DFS\) 序上建主席树就可以了。

查子树查的其实就是 \((dfn[x]-1,dfn[x]+size[x]-1]\)

注意查的是点的编号,但是注意到点权两两不同,就可以离散化后记录每一个点权对应的节点编号了。

主席树注意空间 开成17倍成功RE

\(\\\)

Code


#include<cmath>
#include<cstdio>
#include<cctype>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 100010
#define gc getchar
#define Rg register
#define mid ((l+r)>>1)
using namespace std;

inline int rd(){
  int x=0; bool f=0; char c=gc();
  while(!isdigit(c)){if(c=='-')f=1;c=gc();}
  while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
  return f?-x:x;
}

int n,m,tot,hd[N],val[N],tmp[N],len;

struct edge{int to,nxt;}e[N<<1];

inline void add(int u,int v){
  e[++tot].to=v; e[tot].nxt=hd[u]; hd[u]=tot;
}

int cnt,dfn[N],sz[N],vpos[N],pos[N];

void dfs(int u,int fa){
  sz[u]=1;
  dfn[u]=++cnt;
  vpos[cnt]=val[u];
  for(Rg int i=hd[u],v;i;i=e[i].nxt)
    if((v=e[i].to)!=fa){dfs(v,u);sz[u]+=sz[v];}
}

struct seg{

  int rot[N],ptr;

  inline int newnode(){return ++ptr;}

  struct node{int ls,rs,sum;}c[N*20];

  void build(int &rt,int l,int r){
    rt=newnode();
    if(l==r) return;
    build(c[rt].ls,l,mid);
    build(c[rt].rs,mid+1,r);
  }

  inline void pushup(int rt){
    c[rt].sum=c[c[rt].ls].sum+c[c[rt].rs].sum;
  }

  void insert(int &rt,int lst,int l,int r,int x){
    rt=newnode();
    if(l==r){c[rt].sum=c[lst].sum+1;return;}
    if(x<=mid){
      c[rt].rs=c[lst].rs;
      insert(c[rt].ls,c[lst].ls,l,mid,x);
    }
    else{
      c[rt].ls=c[lst].ls;
      insert(c[rt].rs,c[lst].rs,mid+1,r,x);
    }
    pushup(rt);
  }

  inline int query(int rtl,int rtr,int l,int r,int k){
    if(l==r) return l;
    int nowans=c[c[rtr].ls].sum-c[c[rtl].ls].sum;
    if(k<=nowans) return query(c[rtl].ls,c[rtr].ls,l,mid,k);
    else return query(c[rtl].rs,c[rtr].rs,mid+1,r,k-nowans);
  }

}tree;

int main(){
  n=rd();
  for(Rg int i=1;i<=n;++i) tmp[i]=val[i]=rd();
  sort(tmp+1,tmp+1+n);
  for(Rg int i=1;i<=n;++i){
    tmp[++len]=tmp[i];
    while(tmp[i+1]==tmp[i]) ++i;
  }
  for(Rg int i=1;i<=n;++i){
    val[i]=lower_bound(tmp+1,tmp+1+len,val[i])-tmp;
    pos[val[i]]=i;
  }
  for(Rg int i=1,u,v;i<n;++i){
    u=rd(); v=rd(); add(u,v); add(v,u);
  }
  dfs(1,0);
  tree.build(tree.rot[0],1,len);
  for(Rg int i=1;i<=n;++i) tree.insert(tree.rot[i],tree.rot[i-1],1,len,vpos[i]);
  m=rd();
  for(Rg int i=1,rt,k;i<=m;++i){
    rt=rd(); k=rd();
    printf("%d\n",pos[tree.query(tree.rot[dfn[rt]-1],tree.rot[dfn[rt]+sz[rt]-1],1,len,k)]);
  }
  return 0;
}

转载于:https://www.cnblogs.com/SGCollin/p/9906062.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值