bzoj 3674: 可持久化并查集加强版 主席树

题目大意:
要求维护三种操作。
1   x   y 1\ x\ y 1 x y:合并 x x x y y y所在集合。
2   k 2\ k 2 k:回到第 k k k个操作后的状态。
3   x   y 3\ x\ y 3 x y:询问 x x x y y y是否在一个集合。
强制在线。
n , q ≤ 2 ∗ 1 0 5 n,q≤2*10^5 n,q2105

分析:
如果不考虑路径压缩,使用启发式合并(也就是把小的集合合并到大的集合中)。那么每次只有一个位置的 f a fa fa发生了改变,其他位置都没有变。
由于要可持久化,考虑使用一棵线段树,除了叶子节点外所有点权值都为 0 0 0,叶子节点记录其父亲的节点编号。那么我们可以对当前线段树做一次询问是 l o g n logn logn的,而跳父亲不超过 l o g n logn logn步(每次集合至少增大一倍),所有 g e t f a getfa getfa O ( l o g 2 n ) O(log^2n) O(log2n)的。
我们再开一个主席树维护一下 s i z e size size即可。
总复杂度是 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)的。

代码:

/**************************************************************
    Problem: 3674
    User: ypxrain
    Language: C++
    Result: Accepted
    Time:2052 ms
    Memory:237236 kb
****************************************************************/
 
#include <iostream>
#include <cstdio>
#include <cmath>
 
const int maxn=2e5+7;
 
using namespace std;
 
int n,m,ans,op,x,y;
 
struct tree{
    struct node{
        int l,r,data;
    }t[maxn*50];
    int root[maxn],cnt;
    void build(int &p,int l,int r,int k)
    {
        if (!p) p=++cnt;
        if (l==r)
        {
            t[p].data=k;
            return;
        }
        int mid=(l+r)/2;
        build(t[p].l,l,mid,k);
        build(t[p].r,mid+1,r,k);
    }
    void ins(int &p,int q,int l,int r,int x,int k)
    {
        if (!p) p=++cnt;
        if (l==r)
        {
            t[p].data=k;
            return;
        }
        int mid=(l+r)/2;
        if (x<=mid) t[p].r=t[q].r,ins(t[p].l,t[q].l,l,mid,x,k);
               else t[p].l=t[q].l,ins(t[p].r,t[q].r,mid+1,r,x,k);
    }
    int query(int p,int l,int r,int x)
    {
        if (l==r) return t[p].data;
        int mid=(l+r)/2;
        if (x<=mid) return query(t[p].l,l,mid,x);
               else return query(t[p].r,mid+1,r,x);
    }
}A,B;
 
int getfa(int p,int x)
{
    int u=A.query(A.root[p],1,n,x);
    if (!u) return x;
    return getfa(p,u);
}
 
int main()
{
    scanf("%d%d",&n,&m);
    A.build(A.root[0],1,n,0);
    B.build(B.root[0],1,n,1);   
    for (int i=1;i<=m;i++)
    {
        scanf("%d",&op);
        if (op==1)
        {
            scanf("%d%d",&x,&y);
            x^=ans,y^=ans;
            int u=getfa(i-1,x),v=getfa(i-1,y);
            if (u==v)
            {
                A.root[i]=A.root[i-1],B.root[i]=B.root[i-1];
            }
            else
            {
                int nu=B.query(B.root[i-1],1,n,u);
                int nv=B.query(B.root[i-1],1,n,v);
                if (nu>nv) swap(u,v);
                A.ins(A.root[i],A.root[i-1],1,n,u,v);
                B.ins(B.root[i],B.root[i-1],1,n,v,nu+nv);
            }
        }
        if (op==2)
        {
            scanf("%d",&x);
            x^=ans;
            A.root[i]=A.root[x],B.root[i]=B.root[x];
        }
        if (op==3)
        {
            scanf("%d%d",&x,&y);
            x^=ans,y^=ans;
            A.root[i]=A.root[i-1],B.root[i]=B.root[i-1];
            int u=getfa(i-1,x),v=getfa(i-1,y);
            if (u==v) ans=1;
                 else ans=0;
            printf("%d\n",ans);
        }
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值