BZOJ 2588 Count on a tree (COT) 可持久化线段树

题目大意:查询树上两点之间的第k大的点权。

思路:树套树,其实是正常的树套一个可持久化线段树。因为利用权值线段树可以求区间第k大,然后再应用可持久化线段树的思想,可以做到区间减法。详见代码。

CODE:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define MAX 100010
#define NIL (tree[0])
using namespace std;

pair<int,int> src[MAX];

struct Complex{
	Complex *son[2];
	int cnt;

	Complex(Complex *_,Complex *__,int ___):cnt(___) {
		son[0] = _;
		son[1] = __;
	}
	Complex() {}
}*tree[MAX];

int points,asks;
int xx[MAX];

int head[MAX],total;
int next[MAX << 1],aim[MAX << 1];

int father[MAX][20],deep[MAX];

inline void Add(int x,int y);
void DFS(int x);
void SparseTable();

Complex *BuildTree(Complex *pos,int x,int y,int val);
int GetAns(Complex *l,Complex *r,Complex *f,Complex *p,int x,int y,int k);
int GetLCA(int x,int y);
int Ask(int x,int y,int k);

int main()
{
	cin >> points >> asks;
	for(int i = 1;i <= points; ++i)
		scanf("%d",&src[i].first),src[i].second = i;
	sort(src + 1,src + points + 1);
	for(int i = 1;i <= points; ++i)
		xx[src[i].second] = i;
	for(int x,y,i = 1;i < points; ++i) {
		scanf("%d%d",&x,&y);
		Add(x,y),Add(y,x);
	}
	NIL = new Complex(NULL,NULL,0);
	NIL->son[0] = NIL->son[1] = NIL;
	DFS(1);
	SparseTable();
	int now = 0;
	for(int x,y,k,i = 1;i <= asks; ++i) {
		scanf("%d%d%d",&x,&y,&k);
		printf("%d",now = src[Ask(x ^ now,y,k)].first);
		if(i != asks)	puts("");
	}
	return 0;
}

inline void Add(int x,int y)
{
	next[++total] = head[x];
	aim[total] = y;
	head[x] = total;
}

void DFS(int x)
{
	deep[x] = deep[father[x][0]] + 1;
	tree[x] = BuildTree(tree[father[x][0]],1,points,xx[x]);
	for(int i = head[x];i;i = next[i]) {
		if(aim[i] == father[x][0])	continue;
		father[aim[i]][0] = x;
		DFS(aim[i]);
	}
}

void SparseTable()
{
	for(int j = 1;j <= 19; ++j)
		for(int i = 1;i <= points; ++i)
			father[i][j] = father[father[i][j - 1]][j - 1];
}

int Ask(int x,int y,int k)
{
	int lca = GetLCA(x,y);
	return GetAns(tree[x],tree[y],tree[lca],tree[father[lca][0]],1,points,k);
}

Complex *BuildTree(Complex *pos,int x,int y,int val)
{
	int mid = (x + y) >> 1;
	if(x == y)	return new Complex(NIL,NIL,pos->cnt + 1);
	if(val <= mid)	return new Complex(BuildTree(pos->son[0],x,mid,val),pos->son[1],pos->cnt + 1);
	return new Complex(pos->son[0],BuildTree(pos->son[1],mid + 1,y,val),pos->cnt + 1);
}

int GetLCA(int x,int y)
{
	if(deep[x] < deep[y])	swap(x,y);
	for(int i = 19; ~i; --i)
		if(deep[father[x][i]] >= deep[y])
			x = father[x][i];
	if(x == y)	return x;
	for(int i = 19; ~i; --i)
		if(father[x][i] != father[y][i])
			x = father[x][i],y = father[y][i];
	return father[x][0];
}

int GetAns(Complex *l,Complex *r,Complex *f,Complex *p,int x,int y,int k)
{
	if(x == y)	return x;
	int mid = (x + y) >> 1;
	int temp = l->son[0]->cnt + r->son[0]->cnt - f->son[0]->cnt - p->son[0]->cnt;
	if(k <= temp)	return GetAns(l->son[0],r->son[0],f->son[0],p->son[0],x,mid,k);
	return GetAns(l->son[1],r->son[1],f->son[1],p->son[1],mid + 1,y,k - temp);
}

时间: 2024-08-07 21:18:07

BZOJ 2588 Count on a tree (COT) 可持久化线段树的相关文章

BZOJ 2588 Count on a tree (COT) 是持久的段树

标题效果:两棵树之间的首次查询k大点的权利. 思维:树木覆盖树,事实上,它是正常的树木覆盖了持久段树. 由于使用权值段树可以寻求区间k大,然后应用到持久段树思想,间隔可以做减法.详见代码. CODE: #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define MAX 100010 #define NIL (tree[0]) using names

BZOJ 2588 Count on a tree 主席树+倍增LCA

题目大意:给定一棵树,每个节点有权值,询问两个节点路径上的权值第k小 这题很卡时间... 树链剖分+二分+树套树的O(nlog^4n)做法可以去死了 没有修改操作,树链剖分+二分+划分树O(nlog^3n),还是死了 我怒了,裸学了一发可持久化线段树(不看任何代码OTZ,我是怎么做到的0.0),二分+主席树,O(nlog^2n),居然还是死了! 最后发现我SB了,完全没有必要二分,直接把4个参全传下去就行了,O(nlogn) 首先我们对于每个节点维护这个节点到根的权值线段树 然后对于每个询问(x

bzoj 2588 Count on a tree 解题报告

Count on a tree 题目描述 给定一棵\(N\)个节点的树,每个点有一个权值,对于\(M\)个询问\((u,v,k)\),你需要回答\(u\) \(xor\) \(lastans\)和\(v\)这两个节点间第\(K\)小的点权.其中\(lastans\)是上一个询问的答案,初始为\(0\),即第一个询问的u是明文. 输入输出格式 输入格式: 第一行两个整数\(N,M\). 第二行有\(N\)个整数,其中第\(i\)个整数表示点\(i\)的权值. 后面\(N-1\)行每行两个整数\((

bzoj 2588 Count on a tree

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行,表示每个询问的答案.最后一个询问不输出换行符 S

BZOJ 3207: 花神的嘲讽计划Ⅰ( hash + 可持久化线段树 )

O(NK)暴力搞出所有子串的哈希值, 然后就对哈希值离散化建权值线段树, 就是主席树的经典做法了.总时间复杂度O(NK+(N+Q)logN) -------------------------------------------------------------------------- #include<cstdio> #include<algorithm> #include<cstring> #include<cctype> using namespa

BZOJ 3439 Kpm的MC密码 Trie+可持久化线段树

题目大意:定义一种串,如果一个串是另一个串的后缀,那么这个串称作kpm串.问一个串的标号第k大的kpm串是多少. 思路:将所有的串翻转之后变成前缀,全都插进一个Trie树中.每个节点维护一个last指针,表示最后一次更新的可持久化线段树的指针,如果再有串经过这里,就继续更新last指针.最后只需要查询last指针中的东西就可以了. CODE: #include <cstdio> #include <cstring> #include <iostream> #includ

BZOJ 3123 SDOI2013 森林 可持久化线段树+倍增LCA+启发式合并

题目大意:给定一棵森林,每个点有权值,提供两种操作: 1.查询两点间路径上第k小的权值 2.将两个点之间连一条边 保证连接后仍是一座森林 可持久化线段树部分同Count On A Tree 只是这道题加了个连接操作 对于连接操作我们要用到启发式合并 就是把小的那棵树暴力重建 很简单的一个操作但是可以证明是均摊O(nlogn)的 大小我用了并查集 其实记录根就可以了 此外本题的多组数据是在逗比 记住testcase恒等于1就是了 NND我倍增LCA又写错了0.0 预处理时居然从大往小写的0.0 样

[BZOJ 3207] 花神的嘲讽计划Ⅰ【Hash + 可持久化线段树】

题目链接:BZOJ - 3207 题目分析 先使用Hash,把每个长度为 k 的序列转为一个整数,然后题目就转化为了询问某个区间内有没有整数 x . 这一步可以使用可持久化线段树来做,虽然感觉可以有更简单的做法,但是我没有什么想法... 代码 #include <iostream> #include <cstdio> #include <cstdlib> #include <algorithm> #include <cstring> #inclu

Bzoj 4408: [Fjoi 2016]神秘数 可持久化线段树,神题

4408: [Fjoi 2016]神秘数 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 177  Solved: 128[Submit][Status][Discuss] Description 一个可重复数字集合S的神秘数定义为最小的不能被S的子集的和表示的正整数.例如S={1,1,1,4,13}, 1 = 1 2 = 1+1 3 = 1+1+1 4 = 4 5 = 4+1 6 = 4+1+1 7 = 4+1+1+1 8无法表示为集合S的子集的