您的位置:首页 > 其它

[BZOJ3123][Sdoi2013]森林(主席树+启发式合并)

2016-12-18 17:48 471 查看

=== ===

这里放传送门

=== ===

题解

这题好像很久以前就看过了然后也发现是主席树的启发式合并但是写了半天以后就写得乱七八糟非常愚蠢然后就弃了。。然后前几天又拿出来重写才搞掉它。。

首先对于Q操作,树上主席树就能解决问题;但是如果要把两个连通块连在一起,就要把它们所在的主席树合并。这样的话用启发式合并就可以保证科学的时间复杂度,每次把规模较小的合并到规模较大的里面,这样每一次每个点所在的连通块size都会翻倍,这样的翻倍最多进行O(logn)次,加上每次重新insert带着的一个log,总的时间复杂度是O(log2n)的。

然后就是实现方面的问题了。。以前写树上主席树的时候都是先dfs一遍处理出dfs序再跑到主函数里按照dfs序的顺序建树的,但是如果是这个题的话每次维护dfs序也非常麻烦,所以直接在dfs的过程里面建树就可以了,非常方便其实。。需要注意的一个事情就是每次要把L操作新增的边也加到邻接表里面去,要不然下次再遍历这个连通块的时候就遍历不到新连过去的这块了。。

这题空间耗费出奇的大。。因为没有写垃圾回收之类的东西。。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define Pow 17
using namespace std;
int n,m,T,v[80010],p[80010],a[160010],nxt[160010],root[80010],num[80010],father[80010],deep[80010];
int tot,cnt,size,f[80010][19],anti[80010],lastans,w[80010];
struct segtree{
int l,r,val;
}t[10000010];
void add(int x,int y){
tot++;a[tot]=y;nxt[tot]=p[x];p[x]=tot;
}
int comp(int x,int y){return w[x]<w[y];}
int find(int x){
if (x==father[x]) return x;
father[x]=find(father[x]);
return father[x];
}
void insert(int &i,int j,int l,int r,int x){
i=++size;t[i]=t[j];t[i].val++;
if (l==r) return;
int mid=(l+r)>>1;
if (x<=mid) insert(t[i].l,t[j].l,l,mid,x);
else insert(t[i].r,t[j].r,mid+1,r,x);
}
void dfs(int u,int fa){
deep[u]=deep[fa]+1;
for (int i=1;i<=Pow;i++)
f[u][i]=f[f[u][i-1]][i-1];
insert(root[u],root[fa],1,cnt,v[u]);
for (int i=p[u];i!=0;i=nxt[i])
if (a[i]!=fa){
f[a[i]][0]=u;
dfs(a[i],u);
}
}
int find_lca(int x,int y){
if (deep[x]!=deep[y]){
if (deep[x]<deep[y]) swap(x,y);
for (int i=Pow;i>=0;i--)
if (deep[f[x][i]]>=deep[y])
x=f[x][i];
}
for (int i=Pow;i>=0;i--)
if (f[x][i]!=f[y][i]){
x=f[x][i];y=f[y][i];
}
while (x!=y){
x=f[x][0];y=f[y][0];
}
return x;
}
int query(int i,int j,int lca,int flca,int l,int r,int k){
if (l==r) return l;
int mid=(l+r)>>1,val;
val=t[t[i].l].val+t[t[j].l].val-t[t[lca].l].val-t[t[flca].l].val;
if (k<=val) return query(t[i].l,t[j].l,t[lca].l,t[flca].l,l,mid,k);
else return query(t[i].r,t[j].r,t[lca].r,t[flca].r,mid+1,r,k-val);
}
int main()
{
scanf("%d",&T);
scanf("%d%d%d",&n,&m,&T);
for (int i=1;i<=n;i++){
scanf("%d",&w[i]);p[i]=i;
}
sort(p+1,p+n+1,comp);
for (int i=1;i<=n;i++){
if (w[p[i]]==w[p[i-1]])
v[p[i]]=cnt;
else v[p[i]]=++cnt;
anti[cnt]=w[p[i]];
}
for (int i=1;i<=n;i++){
father[i]=i;num[i]=1;
}
memset(p,0,sizeof(p));
for (int i=1;i<=m;i++){
int x,y,r1,r2;
scanf("%d%d",&x,&y);
add(x,y);add(y,x);
r1=find(x);r2=find(y);
if (r1!=r2){
father[r2]=r1;
num[r1]+=num[r2];
}
}
for (int i=1;i<=n;i++)
if (deep[i]==0){
int rt=find(i);
dfs(rt,0);
}
for (int i=1;i<=T;i++){
char c=getchar();
int x,y,k,lca;
while (c!='L'&&c!='Q') c=getchar();
scanf("%d%d",&x,&y);
x^=lastans;y^=lastans;
if (c=='Q'){
scanf("%d",&k);
k^=lastans;
lca=find_lca(x,y);
lastans=query(root[x],root[y],root[lca],root[f[lca][0]],1,cnt,k);
lastans=anti[lastans];
printf("%d\n",lastans);
}else{
int r1,r2;
add(x,y);add(y,x);//每次都要加边以便下次遍历
r1=find(x);r2=find(y);
if (num[r1]<num[r2]){
swap(x,y);swap(r1,r2);
}
father[r2]=r1;num[r1]+=num[r2];
f[y][0]=x;dfs(y,x);
}
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息