bzoj 3673: 可持久化并查集 by zky

bzoj 3673: 可持久化并查集 by zky

Time Limit: 5 Sec  Memory Limit: 128 MB

Description

n个集合 m个操作
操作:
1 a b 合并a,b所在集合
2 k 回到第k次操作之后的状态(查询算作操作)
3 a b 询问a,b是否属于同一集合,是则输出1否则输出0

0<n,m<=2*10^4

Sample Input

5 6
1 1 2
3 1 2
2 0
3 1 2
2 1
3 1 2

Sample Output

1
0
1
主席树+启发式合并+并查集
以节点编号为下标,以祖先节点为权值建立主席树(也就是主席树内维护祖先节点是谁)
并查集本就是支持合并的
所谓启发式合并就是按秩合并
即朴素的并查集合并时,谁合并谁是随机的
按秩合并,秩即树的深度,深度小的树合并到深度大的树的上面
所以我们要维护一个deep[]数组,记录每个集合的祖先节点的深度(根节点到最深的叶节点的距离)
并查集合并时找到祖先节点,所以只有祖先节点的深度是有效的
那么显然,只有合并两颗深度相同的树时才会更新deep
查找祖先节点查找的比较巧妙
利用主席树维护每个时刻的祖先节点
将朴素的直接查询,改成先到主席树中找到这个节点编号的位置,返回这个位置存储的father
对于回到历史版本,只需令主席树中起指示节点所在线段树位置的root[]=历史版本的root即可
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,cnt;
int root[20001*20],lc[20001*20],rc[20001*20],fa[20001*20],deep[20001*20];
void build(int & k,int l,int r)
{
    k=++cnt;
    if(l==r) {fa[k]=l;return;}
    int mid=l+r>>1;
    build(lc[k],l,mid);
    build(rc[k],mid+1,r);
}
void change(int pre,int & now,int l,int r,int x,int y)
{
    now=++cnt;
    if(l==r) 
    {
        fa[now]=x;
        deep[now]=deep[pre];
        return;
    }
    int mid=l+r>>1;
    if(y<=mid)
    {
        rc[now]=rc[pre];
        change(lc[pre],lc[now],l,mid,x,y);
    }
    else
    {
        lc[now]=lc[pre];
        change(rc[pre],rc[now],mid+1,r,x,y);
    }
}
int query(int y,int l,int r,int w)
{
    if(l==r) return fa[y];
    int mid=l+r>>1;
    if(w<=mid) return query(lc[y],l,mid,w);
    else return query(rc[y],mid+1,r,w);
}
int find(int x,int i)
{
    int p=query(root[i],1,n,x);
    if(p==x) return x;
    return find(p,i);
}
void update(int x,int l,int r,int w)
{
    if(l==r) {deep[x]++;return;}
    int mid=l+r>>1;
    if(w<=mid) update(lc[x],l,mid,w);
    else update(rc[x],mid+1,r,w);
}
void unionn(int x,int y,int i)
{
    if(deep[x]<deep[y]) swap(x,y);
    change(root[i-1],root[i],1,n,x,y);
    if(deep[x]==deep[y]) update(root[x],1,n,x);
}
int main()
{
    scanf("%d%d",&n,&m);
    build(root[0],1,n);
    int x,y,z;
    for(int i=1;i<=m;i++)
    {
        scanf("%d",&z);
        if(z==1)
        {
            root[i]=root[i-1];
            scanf("%d%d",&x,&y);
            x=find(x,i);y=find(y,i);
            unionn(x,y,i);
        }
        else if(z==2)
        {
            scanf("%d",&x);root[i]=root[x];
        }
        else
        {
            scanf("%d%d",&x,&y);
            root[i]=root[i-1];
            x=find(x,i);y=find(y,i);
            printf("%d
",x==y);
        }
    }
}
原文地址:https://www.cnblogs.com/TheRoadToTheGold/p/6527628.html