[SDOI2013]森林(启发式合并)(主席树)
Posted 2017gdgzoi44
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[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 y^lastans。其中^运算符表示异或,等价于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;
}
以上是关于[SDOI2013]森林(启发式合并)(主席树)的主要内容,如果未能解决你的问题,请参考以下文章
BZOJ 3123 [Sdoi2013]森林 主席树启发式合并
主席树 启发式合并bzoj3123: [Sdoi2013]森林
[bzoj3123] [SDOI2013]森林 主席树+启发式合并+LCT