bzoj2588树上主席树

2588: Spoj 10628. Count on a tree

Time Limit: 12 Sec   Memory Limit: 128 MB
Submit: 6896   Solved: 1668
[ Submit][ Status][ Discuss]

Description

给定一棵N个节点的树,每个点有一个权值,对于M个询问(u,v,k),你需要回答u xor lastans和v这两个节点间第K小的点权。其中lastans是上一个询问的答案,初始为0,即第一个询问的u是明文。

Input

第一行两个整数N,M。
第二行有N个整数,其中第i个整数表示点i的权值。
后面N-1行每行两个整数(x,y),表示点x到点y有一条边。
最后M行每行两个整数(u,v,k),表示一组询问。

Output

M行,表示每个询问的答案。最后一个询问不输出换行符

Sample Input

8 5
105 2 9 3 8 5 7 7
1 2
1 3
1 4
3 5
3 6
3 7
4 8
2 5 1
0 5 2
10 5 3
11 5 4
110 8 2

Sample Output

2
8
9
105
7

HINT




HINT:

N,M<=100000

暴力自重。。。
这题刚看被吓一跳。
树上路径权值第K大,还是强制在线,那不是树链剖分+主席树吗?那还写个鬼,回家玩泥巴去。
结果上网搜了一下题解,才发现我真的是沙茶。
反正是静态的主席树,又没有必要一定按照主席树的套路来。既然是树上路径,那我们就按树对主席树进行拓展就好了。
怎么拓展?考虑两个询问的节点u,v,直接构造所有u-v路径肯定是不行的。那么进而考虑特殊情况,如果所有u,v都在同一条到根节点的链上,那么我们可以直接维护每个节点到根节点路径的主席树。那么如果u,v不再同一条到根节点的路径上,转化之即可。
具体来说,就是对于每个节点,其主席树是其到跟节点路径上所有点的主席树,主席树下标是点权,存储的是某个区间点权出现次数(套路)
对于每个询问u,v,先找到u,v的最近公共祖先c和公共祖先的父亲d,那么这条路径可以表示为U+V-C-D其中U,V,C,D分别表示从u,v,c,d到根节点路径上所有的点集合。
这样,只需在做主席树询问的时候把这几条路径对应其主席树的信息调出来查询即可。
发现两个宝贝:
1.主席树可以根据实际情况改变其拓展规则
2.树上路径可以转化为树上节点到根节点的路径。

PS:思考了一下,如果这题要修改的话,可能真的要树剖了。。。

还有,这题格式刷的严格,最后一行不能有空行,注意了。。还有,倍增不要写爆了。。。

贴个代码:

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<cmath>
#define maxn 210000
#define LOGN 20 
using namespace std;
int read()
{
	char ch = getchar(); int x = 0, f = 1;
	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
	return x * f;
}

struct Tree {
	int l, r, sum;
	Tree() : l(0), r(0), sum(0) {}
}t[maxn * LOGN];
int N, n, m, top, tot;
int rank[maxn], a[maxn], b[maxn], deep[maxn], root[maxn], pre[maxn], f[maxn][LOGN + 10];

struct edge {
	int to, next;
	void add(int u, int v)
	{
		to = v;
		next = pre[u];
		pre[u] = top++;
	}
}e[maxn * 2];

void adds(int u, int v)
{
	e[top].add(u, v);
	e[top].add(v, u);
}

void add_tree(int &cur_p, int pre_p, int pos, int L, int R)
{
	t[cur_p = ++ tot] = t[pre_p];
	++t[cur_p].sum;
	if(L == R) return;
	int mid = L + R >> 1;
	if(pos <= mid) add_tree(t[cur_p].l, t[pre_p].l, pos, L, mid);
	else add_tree(t[cur_p].r, t[pre_p].r, pos, mid + 1, R);
}

void dfs(int fa, int u)
{
	f[u][0] = fa;
	deep[u] = deep[fa] + 1;
	add_tree(root[u], root[fa], a[u], 1, n);
	for(int i = pre[u]; ~i; i = e[i].next)
	{
		int v = e[i].to;
		if(v != fa)
			dfs(u, v);
	}
}

void get_f()
{
	for(int k = 1;k < LOGN; ++k)
		for(int i = 1;i <= N; ++i)
			f[i][k] = f[f[i][k - 1]][k - 1];
}

int lca(int u, int v)
{
	if(deep[u] < deep[v]) swap(u, v);
	int h = deep[u] - deep[v];
	for(int i = 0;i < LOGN; ++i)
	if((1 << i) & h) u = f[u][i];
	for(int k = LOGN - 1; ~k; --k)
	if(f[u][k] != f[v][k])
		u = f[u][k], v = f[v][k];
	if(u == v) return u;
	return f[u][0];
}

void init()
{
	n = N = read(); m = read();
	memset(pre, -1, sizeof(pre)); tot = top = 0;
	for(int i = 1;i <= N; ++i) b[i] = a[i] = read();
	sort(b + 1, b + n + 1);
	n = unique(b + 1, b + n + 1) - b - 1;
	for(int i = 1;i <= N; ++i)
	{
		int u = lower_bound(b + 1, b + n + 1, a[i]) - b;
		rank[u] = a[i];
		a[i] = u;
	}
	for(int i = 1;i <= N - 1; ++i)
		adds(read(), read());
	dfs(0, 1);
	get_f();
}

int query(int ut, int vt, int ct, int dt, int L, int R, int k)
{
	if(L == R) return rank[L];
	int mid = L + R >> 1;
	int tmp = t[t[ut].l].sum + t[t[vt].l].sum - t[t[ct].l].sum - t[t[dt].l].sum;
	if(k <= tmp) return query(t[ut].l, t[vt].l, t[ct].l, t[dt].l, L, mid, k);
	else return query(t[ut].r, t[vt].r, t[ct].r, t[dt].r, mid + 1, R, k - tmp);
	
}

void solve()
{
	int last = 0;
	for(int i = 1;i <= m; ++i)
	{
		int u = read() ^ last, v = read(), k = read(), c = lca(u, v);
		printf("%d", last = query(root[u], root[v], root[c], root[f[c][0]], 1, n, k));
		if(i != m) puts("");
	}
}

int main()
{
	init();
	solve();
	return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值