牛客挑戰賽42 C 詢問樹上節點k層兒子的第k大,分層建立主席樹
題目
(https://ac.nowcoder.com/acm/contest/6944/C)
題目描述
小睿睿給了你一顆大小為n的以1為根的樹
每次給定x,k,求x的k代兄弟中第k小的權值
k代兄弟指與他擁有相同的第k代祖先的點(包括自己)
輸入描述:
第1行2個整數n,m
第2行n個整數val[i],表示各節點的權值
第3至n+1行,每行2個整數i,j,表示節點i,j間有一條邊
接下來m行
每行兩個整數a,k ( x = ( a xor lastans ) mod n + 1 )
其中lastans為上一個詢問的答案,其初始值為0,如上個詢問的答案為"?",則為前一個有效答案
輸出描述:
共m行,每行1個整數,表示答案,如果該節點沒有k個兄弟或第k代祖先,輸出"?"(不包含引號)
輸入
5 4
1 5 2 5 4
1 2
2 5
3 5
4 5
1 4
4 2
2 2
1 1
輸出
?
?
5
4
說明
第一個詢問:解碼後x=2,沒有k=4的祖先,答案為?
第二個詢問:解碼後x=5,只有1個k=2兄弟(自己),答案為?
第三個詢問:解碼後x=3,有2個k=2兄弟3,4,權值分別為2,5,第k=2小權值為5,答案為5
第四個詢問:解碼後x=5,有1個k=1兄弟5,權值為4,答案為4
思路
詢問第k級祖先,我們可以用倍增解決。
現在我們考慮解決一個節點的第k層的節點的權值的第k大。
對一個一棵樹:我們先按<深度, 和dfs序>排序,得到一個序列rt[]。
樹上節點在rt的順序如圖:
一個節點x的第k層組先深度就是d=deep[x]+k。
所以我們只要在第d層找到屬於x的子樹上的節點就可以,而且一定是連續的。轉化成區間第k大。
所以我們按rt[]建立主席樹就可以了。
現在我們考慮怎麼在第k層節點中找到屬於x的子節點的區間。
我們每層節點存入一個vector<>。現在對於第d層,dfs序一定是遞增的。
根據dfs序的性質,x的子樹dfs序左區間端點L一定>=dfnx,並且右區間端點R<=dfn[x]+siz[x]-1(x子樹最後一個點的dfs序)
所以L,R只要二分一下就可以了。
#include<bits/stdc++.h> #define LL long long #define mid (l+r>>1) using namespace std; const int N=1e6+10; struct SegTree { int sum[N*40], tot=0; int L[N*40], R[N*40]; void init () { for(int i=0; i<=tot; i++) { L[i]=R[i]=sum[i]=0; } tot=0; } int BT(int l, int r) { int rt=++tot; sum[rt]=0; if(l<r) { L[rt]=BT(l, mid); R[rt]=BT(mid+1, r); } return rt; } int add(int root, int l,int r, int x, int val) {//a[x]+=val int rt=++tot; L[rt]=L[root], R[rt]=R[root], sum[rt]=sum[root]+val; if(l<r) { if(x<=mid) L[rt]=add(L[root], l, mid, x, val); else R[rt]=add(R[root], mid+1, r, x, val); } return rt; } int query(int x, int y, int l, int r, int k) { //區間[x, y]的第k小 if(l>=r) return l;//得到答案 int s=sum[L[y]]-sum[L[x]]; if(s>=k) return query(L[x], L[y], l, mid, k); else return query(R[x], R[y], mid+1, r, k-s); } } Tree; /************************************/ struct Egde { int to, nxt; } e[N<<1]; int head[N], tot; int rt[N];//按<深度, dfn>排序的節點 vector<int> g[N];//儲存每個深度的點 int deep[N], dfn[N], siz[N], T; int cmp(const int x, const int y) { if(deep[x]==deep[y]) { return dfn[x]<dfn[y]; } return deep[x]<deep[y]; } struct Tree_bfs { void add(int x, int y) { e[++tot]= {y, head[x]}; head[x]=tot; } int fa[N][22], lg[N]; void init() { for(int i = 1; i < N; ++i) { lg[i] = lg[i-1] + (1 << lg[i-1] == i); g[i].clear(); } memset(head, 0, sizeof(head)); tot=T=0; } void dfs(int now, int fath) { fa[now][0] = fath; dfn[now]=++T; deep[now] = deep[fath] + 1; siz[now]=1; for(int i = 1; i <= lg[deep[now]]; ++i) fa[now][i] = fa[fa[now][i-1]][i-1]; for(int i = head[now]; i; i = e[i].nxt) if(e[i].to != fath){ dfs(e[i].to, now); siz[now]+=siz[e[i].to]; } } int LCA(int x, int k) {//x的第k級祖先 for(int i=20; i>=0; i--) { if(k&(1<<i)) { x=fa[x][i]; } } return x; } void getrt(int n) { for(int i=1; i<=n; i++) { rt[i]=i; } sort(rt+1, rt+n+1, cmp); for(int i=1; i<=n; i++) { g[deep[rt[i]]].push_back(rt[i]); } } } b; /************************************/ int root[N]; int w[N]; int id[N];//每個點在rt的位置 int main() { Tree.init(); b.init(); int n, m, x, y; scanf("%d%d", &n, &m); for(int i=1; i<=n; i++) { scanf("%d", &w[i]); } for(int i=1; i<n; i++) { scanf("%d%d", &x, &y); b.add(x, y); b.add(y, x); } b.dfs(1, 0); b.getrt(n); root[0]=Tree.BT(1, n); int fa=0, cut=0; for(int i=1; i<=n; i++){ for(auto x: g[i]){ id[x]=++cut; root[x]=Tree.add(root[fa], 1, n, w[x], 1);//按rt陣列建立主席樹 fa=x; } } int last=0; while(m--){ int x, y; scanf("%d%d", &x, &y); x=x^last; x%=n; x++; if(deep[x]<=y){//沒有k級祖先 printf("?\n"); continue; } int lca=b.LCA(x, y); int d=deep[x]; int l=0, r=g[d].size()-1, ql=dfn[lca], qr=dfn[lca]+siz[lca]-1, L=0, R=0; //L, R儲存的是節點 while(l<=r){ int md=g[d][l+r>>1]; if(dfn[md]>=ql){ L=md; r=(l+r>>1)-1; } else{ l=(l+r>>1)+1; } } l=0, r=g[d].size()-1, R=0; while(l<=r){ int md=g[d][l+r>>1]; if(dfn[md]<=qr){ R=md; l=(l+r>>1)+1; } else{ r=(l+r>>1)-1; } } if(id[R]-id[L]+1<y){//節點個數<k printf("?\n"); continue; } last=Tree.query(root[rt[id[L]-1]], root[R], 1, n, y); printf("%d\n", last); } return 0; }