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

=== ===

这里放传送门

=== ===

题解

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

首先对于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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值