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

题目描述
小Z有一片森林,含有N个节点,每个节点上都有一个非负整数作为权值。初始的时候,森林中有M条边。

小Z希望执行T个操作,操作有两类:

Q x y k查询点x到点y路径上所有的权值中,第k小的权值是多少。此操作保证点x和点y连通,同时这两个节点的路径上至少有k个点。
L x y在点x和点y之间连接一条边。保证完成此操作后,仍然是一片森林。
为了体现程序的在线性,我们把输入数据进行了加密。设lastans为程序上一次输出的结果,初始的时候lastans为0。

对于一个输入的操作Q x y k,其真实操作为Q x^lastans y^lastans k^lastans。
对于一个输入的操作L x y,其真实操作为L x^lastans ylastans。其中运算符表示异或,等价于pascal中的xor运算符。
请写一个程序來帮助小Z完成这些操作。

对于所有的数据,n,m,T<=8*10^48∗10
4

输入格式
第一行包含一个正整数testcase,表示当前测试数据的测试点编号。保证1<=testcase<=20。

第二行包含三个整数N,M,T,分别表示节点数、初始边数、操作数。

第三行包含N个非负整数表示 N个节点上的权值。

接下来 M行,每行包含两个整数x和 y,表示初始的时候,点x和点y 之间有一条无向边。

接下来 T行,每行描述一个操作,格式为”Q x y k“或者”L x y “,其含义见题目描述部分。

题目要求的是实现两个操作:查询区间k小值和连边操作。

查询:主席树

连边:LCT

主席树+LCT

首先码量就有一定压力、程序的实现性,但是也不是不能做。

那有没有一些更容易实现的方法呢?

看起来好像区间k小值应该是主席树跑不掉了,那连边操作呢?

好像可以合并主席树。

但又好像直接时间不太行,那怎么办?

答:主席树+LCT

其实可以启发式合并,对每一棵原树记录它的size,每一次合并的时候就相当于将一棵树合并到另一棵树上,启发式合并,将size小的合并到size大的上面去,每次合并的时候用并查集记录,修改时直接找到并查集顶端的元素即可。

题目要求的是树上的区间k小,所以要对于树建主席树,其实就是儿子在爸爸的主席树的基础上添加,跟普通的主席树没有太大的区别,仅仅需要dfs建树罢了。

建树:

void insert(int &hao,int last,int l,int r,int x)//插入点
{
	hao=++cnt;
	tr[hao]=tr[last];
	tr[hao].size++;
	if(l==r)
	{
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid)
	{
		insert(tr[hao].lson,tr[last].lson,l,mid,x);
	}else{
		insert(tr[hao].rson,tr[last].rson,mid+1,r,x);
	}
}
void dfs(int u,int root,int father)//建树(在爸爸的基础上)
{
	fa[u][0]=father;//这里更新lca,查询要用
	for(int i=1;i<=17;i++)
	{
		fa[u][i]=fa[fa[u][i-1]][i-1];
	}
	vis[u]=1;
	size[root]++;
	deep[u]=deep[father]+1;
	fas[u]=father;
	insert(rt[u],rt[father],1,cnt1,lower_bound(b+1,b+cnt1+1,a[u])-b);
	for(int i=head[u];i;i=nxt[i])
	{
		int v=to[i];
		if(v!=father)
		{
			dfs(v,root,u);
		}
	}
}

查询时就是像普通主席树一样。

在这里插入图片描述

题目要求的是一条路径,所以需要四个地方的size:lca的父亲,lca,x,y

像图中这样x到根的路径,y到根的路径减去lca到根的路径,lca的父亲到根的路径,就得到了要求的路径。

这条路径上的点数就是(x.size+y.size-lca.size-lca的父亲.size),同理,左边的主席树的size也可以求的,从而确定是往左找还是往右找。

查询:

int query(int hao,int hao1,int lca,int falca,int l,int r,int k)
{
	if(l==r)
	{
		return b[l];
	}
	int mid=(l+r)/2
	int midsize=tr[tr[hao].lson].size+tr[tr[hao1].lson].size-tr[tr[lca].lson].size-tr[tr[falca].lson].size;//主席树左边的size
	if(midsize>=k)//往左找或往右找
	{
		return query(tr[hao].lson,tr[hao1].lson,tr[lca].lson,tr[falca].lson,l,mid,k);
	}else{
		return query(tr[hao].rson,tr[hao1].rson,tr[lca].rson,tr[falca].rson,mid+1,r,k-midsize);
	}
}

程序:

#pragma GCC optimize(3)
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define N 80010
using namespace std;
struct data
{
	int size,lson,rson;
}tr[N*500];
int to[N<<2],nxt[N<<2],nct,head[N],fas[N],fa[N][18],cnt,rt[N],cnt1,a[N],b[N],size[N],deep[N],n,m,k,x,y,t,lastans;
char s[3];
bool vis[N];
void adde(int x,int y)
{
	to[++nct]=y;
	nxt[nct]=head[x];
	head[x]=nct;
}
int get_fa(int x)//并查集找爸爸
{
	if(fas[x]==x)
	{
		return x;
	}
	return fas[x]=get_fa(fas[x]);
}
void build(int &hao,int l,int r)//建一棵空的树,便于操作
{
	hao=++cnt;
	tr[hao].size=0;
	if(l==r)
	{
		return;
	}
	int mid=(l+r)>>1;
	build(tr[hao].lson,l,mid);
	build(tr[hao].rson,mid+1,r);
}
void insert(int &hao,int last,int l,int r,int x)//插入点
{
	hao=++cnt;
	tr[hao]=tr[last];
	tr[hao].size++;
	if(l==r)
	{
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid)
	{
		insert(tr[hao].lson,tr[last].lson,l,mid,x);
	}else{
		insert(tr[hao].rson,tr[last].rson,mid+1,r,x);
	}
}
void dfs(int u,int root,int father)//建树(在爸爸的基础上)
{
	fa[u][0]=father;//这里更新lca,查询要用
	for(int i=1;i<=17;i++)
	{
		fa[u][i]=fa[fa[u][i-1]][i-1];
	}
	vis[u]=1;
	size[root]++;
	deep[u]=deep[father]+1;
	fas[u]=father;
	insert(rt[u],rt[father],1,cnt1,lower_bound(b+1,b+cnt1+1,a[u])-b);
	for(int i=head[u];i;i=nxt[i])
	{
		int v=to[i];
		if(v!=father)
		{
			dfs(v,root,u);
		}
	}
}
int get_lca(int x,int y)
{
	if(deep[x]<deep[y])
	{
		swap(x,y);
	}
	for(int i=17;i>=0;i--)
	{
		if(deep[fa[x][i]]>=deep[y])
		{
			x=fa[x][i];
		}
	}
	if(x==y)
	{
		return x;
	}
	for(int i=17;i>=0;i--)
	{
		if(fa[x][i]!=fa[y][i])
		{
			x=fa[x][i];
			y=fa[y][i];
		}
	}
	return fa[x][0];
}
int query(int hao,int hao1,int lca,int falca,int l,int r,int k)
{
	if(l==r)
	{
		return b[l];
	}
	int mid=(l+r)/2,midsize=tr[tr[hao].lson].size+tr[tr[hao1].lson].size-tr[tr[lca].lson].size-tr[tr[falca].lson].size;//主席树左边的size
	if(midsize>=k)//往左找或往右找
	{
		return query(tr[hao].lson,tr[hao1].lson,tr[lca].lson,tr[falca].lson,l,mid,k);
	}else{
		return query(tr[hao].rson,tr[hao1].rson,tr[lca].rson,tr[falca].rson,mid+1,r,k-midsize);
	}
}
int main()
{
//	freopen("1.txt","r",stdin);
	scanf("%d",&n);
	scanf("%d%d%d",&n,&m,&t);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		b[i]=a[i];
		fas[i]=i;
	}
	sort(b+1,b+n+1);//离散化
	cnt1=unique(b+1,b+n+1)-b-1;
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&x,&y);
		adde(x,y);
		adde(y,x);
	}
	build(rt[0],1,cnt1);
	for(int i=1;i<=n;i++)
	{
		if(!vis[i])
		{
			dfs(i,i,0);
			fas[i]=i;//重新确定并查集顶端元素,不然会TLE
		}
	}
	while(t--)//操作
	{
		scanf("%s%d%d",s,&x,&y);
		x^=lastans;
		y^=lastans;
		if(s[0]=='Q')
		{
			scanf("%d",&k);
			k^=lastans;
			int lca=get_lca(x,y);
			printf("%d
",lastans=query(rt[x],rt[y],rt[lca],rt[fa[lca][0]],1,cnt1,k));
		}else{
			adde(x,y);
			adde(y,x);
			int xx=get_fa(x),yy=get_fa(y);
			if(size[xx]<size[yy])
			{
				swap(xx,yy);
				swap(x,y);
			}
			dfs(y,xx,x);
		}
	}
	return 0;
}
原文地址:https://www.cnblogs.com/2017gdgzoi44/p/12111006.html