SPOJ_10628_Count on a tree(树上第k大)

题意:

给出一棵树,树上每个节点都有权值,有m个询问,求u到v的路径上第k大的数。

思路:

树上的第k大值,跟区间第k大有些不同,区间第k大每个值在前一个值的基础上新建一棵树,而树上第k大则是在父亲节点的基础上新建一棵树,子节点的主席树都是从底下根节点累加出来的

查询的时候,答案就是root[v] + root[u] - root[lca(v, u)] - root[fa[lca(v,u)]]上的第k大

为什么是这个结果,看这张图就明白了
在这里插入图片描述
红色圈出的分支就对应root[v] - root[fa[lca(u,v)]]
绿色圈出的分支对应root[u] - root[lca]
加在一起就是u - > v的主席树

// 树上的主席树 
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int INF = 0x3f3f3f3f;
typedef long long LL;
const int maxn = 1e5+5;
int A[maxn], B[maxn];
int tot;
// 树 
int head[maxn], cnt1;
struct Edge{
	int v, next;
}edges[maxn<<1];
void addEdge(int u, int v){
	edges[cnt1].v = v;
	edges[cnt1].next = head[u]; head[u] = cnt1++;
}

// 主席树
int lson[maxn<<5], rson[maxn<<5], Tree[maxn<<5], root[maxn];

int cnt;
void Build(int& rt, int l, int r){
	rt = ++cnt; Tree[rt] = 0;
	if(l == r) return;
	int mid = (l+r) >> 1;
	Build(lson[rt], l, mid);
	Build(rson[rt], mid+1, r);
}

void update(int& new_rt, int old_rt, int l, int r, int pos){
	new_rt = ++cnt;
	lson[new_rt] = lson[old_rt]; rson[new_rt] = rson[old_rt];
	Tree[new_rt] = Tree[old_rt] + 1;
	if(l == r) return;
	int mid = (l+r) >> 1;
	if(pos <= mid) update(lson[new_rt], lson[old_rt], l, mid, pos);
	else update(rson[new_rt], rson[old_rt], mid+1, r, pos);
}

int Query(int u, int v, int lca, int fa_lca, int l, int r, int k){
	if(l == r) return l;
	int mid = (l+r) >> 1;
	int leftot = Tree[lson[u]] + Tree[lson[v]] - Tree[lson[lca]] - Tree[lson[fa_lca]];
	if(k <= leftot) return Query(lson[u], lson[v], lson[lca], lson[fa_lca], l, mid, k);
	return Query(rson[u], rson[v], rson[lca], rson[fa_lca], mid+1, r, k-leftot);
}

bool vis[maxn];
int num;				// 计数访问次序
int vis_order[maxn];	// vis_order[x]: x节点的访问次序 
int ver[maxn<<1];		// ver[x]: 第x个访问的是哪个节点
int depth[maxn<<1];		// depth[x]: 第x个访问的节点的深度
int fa[maxn];			// fa[x]:x的父节点
void dfs(int u, int dad, int level){
	vis[u] = 1; vis_order[u] = ++num; ver[num] = u; depth[num] = level; fa[u] = dad;
	
	update(root[u], root[dad], 1, tot, A[u]); 	//在父节点的基础上建树
	
	for(int i = head[u]; i != -1; i = edges[i].next){
		int v = edges[i].v;
		if(!vis[v]){
			dfs(v, u, level+1);
			ver[++num] = u; depth[num] = level;		// 回溯上来又经过u 
		}
	}
}

int dp[20][maxn<<1];
void ST(int n){
	for(int i = 1; i <= n; ++i) dp[0][i] = i;
	for(int i = 1; (1<<i) <= n; ++i)
		for(int j = 1; j + (1<<i) -1 <= n; ++j)
			dp[i][j] = depth[dp[i-1][j]] < depth[dp[i-1][j+(1<<(i-1))]] ? dp[i-1][j] : dp[i-1][j+(1<<(i-1))];
}
int RMQ(int l, int r){
	int k = log(r-l+1) / log(2);
	return depth[dp[k][l]] < depth[dp[k][r-(1<<k)+1]] ? dp[k][l] : dp[k][r-(1<<k)+1];
}
// 求最近公共祖先
int LCA(int u, int v){
	u = vis_order[u]; v = vis_order[v];
	if(u > v) swap(u, v);
	int t = RMQ(u, v);
	return ver[t];
}

int main()
{
	int n, m;
	int u,v,k;
	while(scanf("%d%d",&n,&m) == 2&&n){
		for(int i = 1; i <= n; ++i) scanf("%d", &A[i]), B[i] = A[i];
		// 离散化
		sort(B+1, B+n+1);
		tot = unique(B+1, B+n+1) - (B+1);
		for(int i = 1; i <= n; ++i) A[i] = lower_bound(B+1, B+tot+1, A[i]) - B;
		
		// 读入边 
		cnt1 = 0;
		memset(head, -1, sizeof(head));
		for(int i = 1; i < n; ++i){
			scanf("%d%d",&u,&v);
			addEdge(u, v); addEdge(v, u);
		}
		
		// 建主席树 
		cnt = 0;
		Build(root[0], 1, tot);	// 初始化 
		num = 0;
		memset(vis, 0, sizeof(vis));
		dfs(1, 0, 1);			// 子树在父节点的基础上建立
		
		//for(int i = 1; i <= 2*n-1; ++i) printf("%d ", depth[i]) ;puts("");
		// LCA初始化 
		ST(2*n-1);
		//for(int i = 1; i <= 2*n-1; ++i) printf("%d ", dp[1][i]) ;
		// 查询
		for(int i = 1; i <= m; ++i){
			scanf("%d%d%d", &u,&v,&k);
			int lca = LCA(u, v);
			int t = Query(root[u], root[v], root[lca], root[fa[lca]], 1, tot, k);
			printf("%d\n", B[t]);
		}
	}
	fclose(stdin);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值