牛客挑战赛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一定>=dfn[x] (x子树的一个点的dfs序),并且右区间端点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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)