码迷,mamicode.com
首页 > 其他好文 > 详细

bzoj-3123 森林

时间:2015-10-21 12:41:06      阅读:207      评论:0      收藏:0      [点我收藏+]

标签:bzoj   lca   启发式合并   线段树   主席树   

题意:

给出一个n个点m条边的森林,每个点有一个点权,有两种操作;

1.查询两点之间的第K小的点权,保证合法;

2.连边(x,y);

m<n<=80000;

题解:

论正确姿势的重要性;

首先询问和某道COT的题很像,而这道题中多了Link操作;

然而,那道COT的题我是用树链剖分写的。。。

一开始的脑洞是每次将小的暴力重构作为一个轻链连在大的树上,然后每隔一段时间重构一次大树;

听起来十分暴力了。。鬼知道能不能过;

而正解是COT的正解的姿势,将一个询问分成四个,利用主席树的可减性质,找到LCA然后+x+y-LCA(x,y)-FA(LCA(x,y))像这样处理就可以了;

重构是差不多的,也是启发式合并,维护一下倍增LCA;

注意倍增LCA的数组要清。。小心RE,还是说那么写LCA的只有我一个= =?

空间给的足够的大,所以不需要写垃圾回收了,实际上因为每次重构的是整棵子树,回收是可以的但是有些麻烦。。;

总之还是一道挺好的题(笑);


代码:


#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 81000
#define M 20002000
#define lson l,mid,ls[no]
#define rson mid+1,r,rs[no]
using namespace std;
typedef long long ll;
int next[N<<1],to[N<<1],head[N],ce;
int val[N],dis[N],len;
int deep[N],fa[N][22],root[N];
int size[M],ls[M],rs[M],tot;
char op[10];
namespace Set
{
	int size[N],f[N];
	void init(int n)
	{
		for(int i=1;i<=n;i++)
			size[i]=1,f[i]=i;
	}
	int find(int x)
	{
		return f[x]==x?x:f[x]=find(f[x]);
	}
	void Link(int x,int y)
	{
		x=find(x),y=find(y);
		if(x!=y)
		{
			if(size[x]>size[y])
				swap(x,y);
			size[y]+=size[x];
			f[x]=y;
		}
	}
}
int newnode()
{
	static int ret;
	ret=++tot;
	ls[ret]=rs[ret]=size[ret]=0;
	return ret;
}
void Insert(int l,int r,int &no,int val)
{
	int p=newnode();
	ls[p]=ls[no],rs[p]=rs[no],size[p]=size[no];
	no=p;
	size[no]++;
	if(l==r)	return ;
	int mid=l+r>>1;
	if(val<=mid)	Insert(lson,val);
	else			Insert(rson,val);
}
int query(int l,int r,int no1,int no2,int no3,int no4,int k)
{
	if(l==r)
		return l;
	else
	{
		int mid=l+r>>1;
		if(k<=size[ls[no1]]+size[ls[no2]]-size[ls[no3]]-size[ls[no4]])
			return query(l,mid,ls[no1],ls[no2],ls[no3],ls[no4],k);
		else
			return query(mid+1,r,rs[no1],rs[no2],rs[no3],rs[no4],
				k-size[ls[no1]]-size[ls[no2]]+size[ls[no3]]+size[ls[no4]]);
	}
}
void dfs(int x)
{
	deep[x]=deep[fa[x][0]]+1;
	root[x]=root[fa[x][0]];
	Insert(1,len,root[x],val[x]);
	memset(fa[x]+1,0,sizeof(int)*21);
	for(int i=1;fa[x][i-1];i++)
		fa[x][i]=fa[fa[x][i-1]][i-1];
	for(int i=head[x];i;i=next[i])
	{
		if(to[i]!=fa[x][0])
		{
			fa[to[i]][0]=x;
			dfs(to[i]);
		}
	}
}
int LCA(int x,int y)
{
	if(x==y)	return x;
	if(deep[x]<deep[y])
		swap(x,y);
	int k=20;
	while(k>=0)
	{
		if(deep[fa[x][k]]>=deep[y])
			x=fa[x][k];
		k--;
	}
	if(x==y)	return x;
	k=20;
	while(k>=0)
	{
		if(fa[x][k]!=fa[y][k])
			x=fa[x][k],y=fa[y][k];
		k--;
	}
	return fa[x][0];
}
void add(int x,int y)
{
	to[++ce]=y;
	next[ce]=head[x];
	head[x]=ce;
}
int main()
{
	int n,m,q,i,j,k,x,y,v,fx,fy,last;
	scanf("%*d");
	scanf("%d%d%d",&n,&m,&q);
	Set::init(n);
	for(i=1;i<=n;i++)
		scanf("%d",val+i),dis[i]=val[i];
	sort(dis+1,dis+n+1);
	len=unique(dis+1,dis+n+1)-dis-1;
	for(i=1;i<=n;i++)
		val[i]=lower_bound(dis+1,dis+len+1,val[i])-dis;
	for(i=1;i<=m;i++)
	{
		scanf("%d%d",&x,&y);
		add(x,y),add(y,x);
		Set::Link(x,y);
	}
	for(i=1;i<=n;i++)
	{
		if(!deep[i])
			dfs(i);
	}
	for(i=1,last=0;i<=q;i++)
	{
		scanf("%s%d%d",op,&x,&y);
		x^=last,y^=last;
		if(op[0]=='Q')
		{
			scanf("%d",&k);
			k^=last;
			v=LCA(x,y);
			last=dis[query(1,len,root[x],root[y],root[v],root[fa[v][0]],k)];
			printf("%d\n",last);
		}
		else
		{
			fx=Set::find(x),fy=Set::find(y);
			if(Set::size[fx]>Set::size[fy])
				swap(x,y),swap(fx,fy);
			Set::Link(x,y);
			fa[x][0]=y;
			dfs(x);
			add(x,y),add(y,x);
		}
	}
	return 0;
}



bzoj-3123 森林

标签:bzoj   lca   启发式合并   线段树   主席树   

原文地址:http://blog.csdn.net/ww140142/article/details/49301277

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!