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;
}


你可能感兴趣的:(线段树,bzoj,可持久化线段树,启发式合并,SDOI2013)