BZOJ 3123 SDOI 2013 森林 可持久化线段树+启发式合并

题目大意:给出一个森林,每个节点都有一个权值。有若干加边操作,问两点之间路径上的第k小权值是多少。

思路:这题和COT1比较像,但是多了连接操作。这样就只能暴力合并连个树。启发式合并会保证时间复杂度不至于太大。然后就是用可持久化线段树维护一个树的信息,按照dfs序来建树,每个节点的可持久化链的参考版本就是它父亲的版本。之后利用权值线段树可区间加减的特性,用f[x] + f[y] - f[lca] - f[father[lca]]来计算权值。

CODE:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define MAX 80010
#define MAX_RANGE 1000000000
#define POOL_SIZE 20000000
using namespace std;

struct PersSegTree{
	PersSegTree *son[2];
	int num;
}mempool[POOL_SIZE],*C = mempool;

int points,edges,asks;
int src[MAX];
int head[MAX],total;
int next[MAX << 1],aim[MAX << 1];

int f[MAX],cnt[MAX];

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

PersSegTree *tree[MAX];

char s[10];

PersSegTree *NewNode(PersSegTree *_,PersSegTree *__,int ___)
{
	C->son[0] = _;
	C->son[1] = __;
	C->num = ___;
	return C++;
}

void Initialize()
{
	for(int i = 1;i <= points; ++i)
		f[i] = i,cnt[i] = 1;
	tree[0] = NewNode(C,C,0);
}

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

int Find(int x)
{
	if(f[x] == x)	return x;
	return f[x] = Find(f[x]);
}

void Unite(int x,int y)
{
	int fx = Find(x);
	int fy = Find(y);
	cnt[fx] += cnt[fy];
	f[fy] = fx;
}

void SparseTable()
{
	for(int j = 1;j <= 19; ++j)
		for(int i = 1;i <= top; ++i)
			father[xx[i]][j] = father[father[xx[i]][j - 1]][j - 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];
}

PersSegTree *BuildTree(PersSegTree *consult,int l,int r,int val)
{
	if(l == r)	return NewNode(NULL,NULL,consult->num + 1);
	int mid = (l + r) >> 1;
	if(val <= mid)
		return NewNode(BuildTree(consult->son[0],l,mid,val),consult->son[1],consult->num + 1);
	else
		return NewNode(consult->son[0],BuildTree(consult->son[1],mid + 1,r,val),consult->num + 1);
}

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

int GetKth(PersSegTree *_l,PersSegTree *_r,PersSegTree *f,PersSegTree *p,int l,int r,int k)
{
	if(l == r)	return l;
	int mid = (l + r) >> 1;
	int temp = _l->son[0]->num + _r->son[0]->num - f->son[0]->num - p->son[0]->num;
	if(k <= temp)	return GetKth(_l->son[0],_r->son[0],f->son[0],p->son[0],l,mid,k);
	return GetKth(_l->son[1],_r->son[1],f->son[1],p->son[1],mid + 1,r,k - temp);
}

int main()
{
	scanf("%*d%d%d%d",&points,&edges,&asks);
	Initialize();
	for(int i = 1;i <= points; ++i)
		scanf("%d",&src[i]);
	for(int x,y,i = 1;i <= edges; ++i) {
		scanf("%d%d",&x,&y);
		Add(x,y),Add(y,x);
		Unite(x,y);
	}
	for(int i = 1;i <= points; ++i)
		if(!deep[i])	DFS(i,0);
	SparseTable();
	int last_ans = 0;
	for(int x,y,z,i = 1;i <= asks; ++i) {
		scanf("%s%d%d",s,&x,&y);
		x ^= last_ans,y ^= last_ans;
		if(s[0] == 'Q') {
			scanf("%d",&z);
			z ^= last_ans;
			int lca = GetLCA(x,y);
			printf("%d\n",last_ans = GetKth(tree[x],tree[y],tree[lca],tree[father[lca][0]],0,MAX_RANGE,z));
		}
		else {
			int fx = Find(x);
			int fy = Find(y);
			if(cnt[fx] > cnt[fy])	swap(x,y);
			top = 0;
			DFS(x,y);
			Unite(x,y);
			Add(x,y),Add(y,x);
			SparseTable();
		}
	}
	return 0;
}

时间: 2024-10-16 16:26:14

BZOJ 3123 SDOI 2013 森林 可持久化线段树+启发式合并的相关文章

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

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

p3302 [SDOI2013]森林(树上主席树+启发式合并)

对着题目yy了一天加上看了一中午题解,终于搞明白了我太弱了 连边就是合并线段树,把小的集合合并到大的上,可以保证规模至少增加一半,复杂度可以是\(O(logn)\) 合并的时候暴力dfs修改倍增数组和维护主席树即可 然后树上主席树就是维护节点到根节点的信息即可, 询问链上的第k大时,画图后可以发现维护一个rootx,rooty,rootlca和rootfalca即可解决问题 注意空间要开够 然后注意细节,没了 #include <cstdio> #include <cstring>

BZOJ 2653 middle 二分答案+可持久化线段树

题目大意:给定一个长度为n的序列,求当子序列s的左端点在[a,b],右端点在[c,d]时的最大中位数 其中当序列长度为偶数时中位数定义为中间两个数中较大的那个 很难想的一道题 具体题解见 http://blog.csdn.net/acm_cxlove/article/details/8566093 说的很详细 区间处理那里 [b,c]是必选的 [a,b)和(c,d]每段取最大加和 否则re恒>=0 #include<cstdio> #include<cstring> #inc

BZOJ 3932 CQOI2015 任务查询系统 可持久化线段树

题目大意见http://pan.baidu.com/s/1o6zajc2 主席树裸上就好了... #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define M 100100 using namespace std; struct Segtree{ Segtree *ls,*rs; int size; long long sum; void* op

HDU 5029 Relief grain(离线+线段树+启发式合并)(2014 ACM/ICPC Asia Regional Guangzhou Online)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5029 Problem Description The soil is cracking up because of the drought and the rabbit kingdom is facing a serious famine. The RRC(Rabbit Red Cross) organizes the distribution of relief grain in the disa

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

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

[BZOJ 3218] A + B Problem 【可持久化线段树 + 网络流】

题目连接:BZOJ - 3218 题目分析 题目要求将 n 个点染成黑色或白色,那么我们可以转化为一个最小割模型. 我们规定一个点 i 最后属于 S 集表示染成黑色,属于 T 集表示染成白色,那么对于每个点 i 就要连边 (S, i, B[i]) 和 (i, T, W[i]). 这样,如果一个点属于 S 集,就要割掉与 T 相连的边,就相当于失去了染成白色的收益. 我们再来考虑 “奇怪的点”,一个点 i 变成奇怪的点的条件是:i 是黑色且存在一个白色点 j 满足 j < i && L

【BZOJ 3674】可持久化并查集加强版&amp;【BZOJ 3673】可持久化并查集 by zky 用可持久化线段树破之

最后还是去掉异或顺手A了3673,,, 并查集其实就是fa数组,我们只需要维护这个fa数组,用可持久化线段树就行啦 1:判断是否属于同一集合,我加了路径压缩. 2:直接把跟的值指向root[k]的值破之. 3:输出判断即可. 难者不会,会者不难,1h前我还在膜这道题,现在吗hhh就当支持下zky学长出的题了. 3673: #include<cstdio> #include<cstring> #include<algorithm> #define read(x) x=ge

[TS-A1505] [清橙2013中国国家集训队第二次作业] 树 [可持久化线段树,求树上路径第k大]

按Dfs序逐个插入点,建立可持久化线段树,每次查询即可,具体详见代码. 不知道为什么,代码慢的要死,, #include <iostream> #include <algorithm> #include <cstdio> #include <cstdlib> #include <cstring> #include <cmath> #include <ctime> #include <vector> using