SPOJ PT07J - Query on a tree III(划分树)

PT07J - Query on a tree III

 

You are given a node-labeled rooted tree with n nodes.

Define the query (x, k): Find the node whose label is k-th largest in the subtree of the node x. Assume no two nodes have the same labels.

Input

The first line contains one integer n (1 <= n <= 105). The next line contains n integers li (0 <= li <= 109) which denotes the label of the i-th node.

Each line of the following n - 1 lines contains two integers u, v. They denote there is an edge between node u and node v. Node 1 is the root of the tree.

The next line contains one integer m (1 <= m <= 104) which denotes the number of the queries. Each line of the next m contains two integers x, k. (k <= the total node number in the subtree of x)

Output

For each query (x, k), output the index of the node whose label is the k-th largest in the subtree of the node x.

Example

Input:
5
1 3 5 2 7
1 2
2 3
1 4
3 5
4
2 3
4 1
3 2
3 2

Output:
5
4
5
5
【分析】给你一棵树以及每个节点的权值,求某个节点以及往下子孙中第K大的节点是哪个。
先DFS给每个节点编号,然后对于每个节点记录他的开始编号和结束编号,然后就是划分树模板了。
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <time.h>
#include <string>
#include <map>
#include <stack>
#include <vector>
#include <set>
#include <queue>
#define met(a,b) memset(a,b,sizeof a)
#define pb push_back
#define lson(x) ((x<<1))
#define rson(x) ((x<<1)+1)
using namespace std;
typedef long long ll;
const int N=1e5+5;
const int M=1e6+10;
int le[N],re[N],lab[N],sorted[N];
int n,m,k,num=0;
map<int,int>mp;
struct Edge {
    int to,next;
} edg[N*2];
int head[N],tot;
void init() {
    tot = 0;
    memset(head,-1,sizeof(head));
}
void addedge(int u,int v) {
    edg[tot].to = v;
    edg[tot].next = head[u];
    head[u] = tot++;
}
void dfs(int u,int fa) {
    le[u]=++num;
    sorted[num]=lab[u];
    for(int i=head[u]; i!=-1; i=edg[i].next) {
        int v=edg[i].to;
        if(v!=fa) {
            dfs(v,u);
        }
    }
    re[u]=num;
}
int tree[20][N];
int toleft[20][N];
void build(int l,int r,int dep) {
    if(l==r)return;
    int mid=(l+r)>>1;
    int same=mid-l+1;
    for(int i=l; i<=r; i++)
        if(tree[dep][i]<sorted[mid])
            same--;
    int lpos=l;
    int rpos=mid+1;
    for(int i=l; i<=r; i++) {
        if(tree[dep][i]<sorted[mid]) { //去左边
            tree[dep+1][lpos++]=tree[dep][i];
        } else if(tree[dep][i]==sorted[mid]&&same>0) { //去左边
            tree[dep+1][lpos++]=tree[dep][i];
            same--;
        } else //去右边
            tree[dep+1][rpos++]=tree[dep][i];
        toleft[dep][i]=toleft[dep][l-1]+lpos-l;//从1到i放左边的个数
    }
    build(l,mid,dep+1);//递归建树
    build(mid+1,r,dep+1);
}
void initBuild() {
    for(int i=0; i<20; i++)tree[i][0]=toleft[i][0]=0;
    for(int i=1; i<=n; i++) {
        tree[0][i]=sorted[i];
    }
    sort(sorted+1,sorted+n+1);
    build(1,n,0);
}

int query(int L,int R,int l,int r,int dep,int k) {
    if(l==r)return tree[dep][l];
    int mid=(L+R)>>1;
    int cnt=toleft[dep][r]-toleft[dep][l-1];
    if(cnt>=k) {
        //L+查询区间前去左边的数的个数
        int newl=L+toleft[dep][l-1]-toleft[dep][L-1];
        //左端点+查询区间会分入左边的数的个数
        int newr=newl+cnt-1;
        return query(L,mid,newl,newr,dep+1,k);//注意
    } else {
        //r+区间后分入左边的数的个数
        int newr=r+toleft[dep][R]-toleft[dep][r];
        //右端点减去区间分入右边的数的个数
        int newl=newr-(r-l-cnt);
        return query(mid+1,R,newl,newr,dep+1,k-cnt);//注意
    }
}
int main() {
    int u,v,x;
    init();
    scanf("%d",&n);
    for(int i=1; i<=n; i++){
        scanf("%d",&lab[i]);
        mp[lab[i]]=i;
    }
    for(int i=1; i<n; i++) {
        scanf("%d%d",&u,&v);
        addedge(u,v);
        addedge(v,u);
    }
    dfs(1,0);
    initBuild();
    scanf("%d",&m);
    while(m--){
        scanf("%d%d",&x,&k);
        u=le[x];v=re[x];
        int ans=query(1,n,u,v,0,k);
        printf("%d\n",mp[ans]);
    }
    return 0;
}

 


转载于:https://www.cnblogs.com/jianrenfang/p/6429425.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值