牛客小白月赛14 H-图上计数(主席树求子树上第k大)

题目描述
给定一棵以 1 为根,大小为 N 的树,每个点有若干条出边,如果你要
dfs 的话,它们需要按照输入的顺序去遍历(就是指定了dfs 序)
你需要维护一种数据结构,支持两种操作:
1 x:将 x 为根的子树中的节点,以dfs 序为下标,编号大小为关键字排序。相当于树的形态不变,但是子树里节点的编号排了遍序,dfs 序小的对应编号小的,dfs 序大的对应编号大的
2 x:查询原先树中点 x 现在的编号是多少
一共会给出 Q 个询问。 (N,Q <= 1e5)

题解:
操作1是对子树排序,相当于缩点,缩在一起的结点是排好序的。先处理出每个点的dfs序号,每次查询u,如果u没有缩过点,说明u的任一父节点都没有进行过操作也,也就是说u的位置没有变化,直接输出u。如果u缩过点,找到和u在同一个集合内深度最小的点,这个点为根的子树全部排好序了。k为u原先在这个子树上是第几个dfs到的,主席树查询子树上第k大就得到了排完序之后该结点上的编号。

AC代码:

#include<bits/stdc++.h>
#define mid ((l+r)>>1)
using namespace std;
const int maxn = 1e5 + 50;
vector<int> g[maxn];
int dfn[maxn];
int id[maxn];
int sz[maxn];
int T[maxn*20],ls[maxn*20],rs[maxn*20],s[maxn*20];
int dex;
int n,q;
void dfs(int u){
	dfn[++dex] = u, id[u] = dex, sz[u] = 1;//出过的bug:++dex写成dex++ 
	for(int i = 0;i < g[u].size();++i) {
		dfs(g[u][i]);sz[u]+=sz[g[u][i]];
	}
}
void init(){
	scanf("%d%d",&n,&q);
	for(int u = 1;u <= n;++u){
		int k;scanf("%d",&k);
		while(k--){
			int v;scanf("%d",&v);
			g[u].push_back(v);
		}
	}
	dex = 0;
	dfs(1);
}
int tot;
void build(int pre,int cur,int l,int r,int pos){
	s[cur] = s[pre] + 1, ls[cur] = ls[pre], rs[cur] = rs[pre];
	if(l == r) return;
	if(pos <= mid) 
		build(ls[pre], ls[cur] = ++tot, l, mid, pos);
	else 
		build(rs[pre], rs[cur] = ++tot, mid+1, r, pos);
}
int query(int pre,int cur,int l,int r,int k){
	if(l == r) return l;
	int res = s[ls[cur]] - s[ls[pre]];
	if(res >= k) 
		return query(ls[pre], ls[cur], l, mid, k);
	else 
		return query(rs[pre], rs[cur], mid + 1, r, k - res);
}
int fa[maxn];
int fnd(int x){if(fa[x] == x) return x;return fa[x] = fnd(fa[x]);}
void modify(int u,int acc){
	if(fa[u]){
		fa[fnd(u)] = acc;return;
	}
	fa[u] = acc;
	for(int i = 0;i < g[u].size();++i) modify(g[u][i],acc);
}
int query(int u){
	if(!fa[u]) return u;//没被缩过,编号没有变化
	int f = fnd(u);
	return query(T[id[f] - 1],T[id[f] + sz[f] - 1],1,n,id[u] - id[f] + 1); 
}
void sol(){
	tot = 0;
	for(int i = 1;i <= n;++i) {
		build(T[i-1],T[i] = ++tot,1,n,dfn[i]);
	}
	while(q--){
		int op,x;scanf("%d%d",&op,&x);
		if(op == 1){
			if(fa[x]) continue;//之前缩过点,则已经有序 
			modify(x,x);
		}
		else{
			printf("%d\n",query(x));
		}
	}
}
int main(){
	init();sol();
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值