可持久化数组到可持久化并查集

最水的文章,没有之一!主要是因为太简单了。

可持久化数组

实现功能

  • 查询并复制历史版本数组
  • 修改并复制历史版本数组

实现方法

具体可以见我的博客文章:主席树。

我们按照类似的思想(不需要构建权值线段树),将初始数组直接建成一个线段树就好了,然后修改类似。

  • 问:为什么不直接用原数组,修改某一位置,直接连一个新的点呢?
    答:因为这样达不到快速查询的要求。而要存储空间小,快速查询,我们只能在二者中取权衡,并使得时间也在可承受范围内。线段树就恰好满足这样的性质。

那么就很容易写出来了:

#include <cstdio>
#include <algorithm>
#define MAXN 1000010

using namespace std;

int a[40*MAXN], L[40*MAXN], R[40*MAXN], root[40*MAXN], w[MAXN];
int cnt, N, M;

inline void read(int &x)
{
    x = 0;
    int f = 1;
    char ch = getchar();
    for(; ch < '0' || ch > '9'; ch = getchar())
        if(ch == '-')
        {
            f = -1;
            ch = getchar();
            break;
        }
    for(; ch >= '0' && ch <= '9'; ch = getchar())
        x = (x<<3) + (x<<1) + ch - '0';
    x *= f;
}

void Build(int &rt, int l, int r)
{
    rt = ++cnt;
    if(l == r)
    {
        a[rt] = w[l];///l代表了当前点编号,rt是它在持久数组上的位置
        return;
    }
    int mid = l + r >> 1;
    Build(L[rt], l, mid);
    Build(R[rt], mid+1, r);
}

void Change(int ver, int &rt, int l, int r, int pos, int val)
{
    rt = ++cnt;
    L[rt] = L[ver];
    R[rt] = R[ver];//别忘了,博主就被坑了
    if(l == r)
    {
        a[rt] = val;
        return;
    }
    int mid = l + r >> 1;
    if(pos <= mid)
        Change(L[ver], L[rt], l, mid, pos, val);
    else
        Change(R[ver], R[rt], mid+1, r, pos, val);
}

///查找编号rt点在持久数组的下标
int Query(int rt, int l, int r, int pos)
{
    if(l == r)
        return rt;
    int mid = l + r >> 1;
    if(pos <= mid)
        return Query(L[rt], l, mid, pos);
    else
        return Query(R[rt], mid+1, r, pos);
}

int main()
{
    int opt, v, val, pos, posa;
    read(N);
    read(M);
    for(register int i = 1; i <= N; i += 1)
        read(w[i]);
    Build(root[0], 1, N);
    for(register int i = 1; i <= M; i += 1)
    {
        read(v);
        read(opt);
        read(pos);
        root[i] = root[v];
        if(opt == 1)
        {
            read(val);
            Change(root[v], root[i], 1, N, pos, val);
        }
        else
        {
            posa = Query(root[i], 1, N, pos);
            printf("%d\n", a[posa]);
        }
    }
    return 0;
}

洛谷测评,最慢测试点耗时:1094ms。

可持久化并查集

实现功能

  • 合并集合
  • 回到历史版本(所有集合)
  • 询问是否同集合

实现方法

因为并查集是自底向上的,因此不能直接做类似开点的方法达到可持久化的目的,因此我们需要转化。

聪明的大家除了我都知道,转化的方法就是弄成可持久化数组。有人可能会问了:这怎么可能联系到的?其实并不难。

你只需要维护一下数组中每个节点的父节点就差不多了(仅仅是差不多而已),注意,这里不能路径压缩,不然会乱套的(比如回到历史版本的时候你根本搞不清哪些节点该跟谁走,跟错了爸爸还得了)。但是不进行路径压缩的话,怎么办呢?不会超时间吗?这有办法,就是将深度小的集合(其实差不多是树了)在合并的时候合并到深度大的里面去,可以将树的深度维持在接近对数级别的范围。

那么我们剩下的工作就是和可持久化数组差不多了,代码也比较好懂,大家看代码读细节吧233。

#include <cstdio>
#include <algorithm>
#define MAXN 100010

using namespace std;

int fa[30*MAXN], L[30*MAXN], R[30*MAXN], de[30*MAXN], root[30*MAXN];
int cnt, N, M;

inline void read(int &x)
{
    x = 0;
    char ch = getchar();
    for(; ch < '0' || ch > '9'; ch = getchar());
    for(; ch >= '0' && ch <= '9'; ch = getchar())
        x = (x<<3) + (x<<1) + ch - '0';
}

void Build(int &rt, int l, int r)
{
    rt = ++cnt;
    if(l == r)
    {
        fa[rt] = l;///l代表了当前点编号,rt是它在持久数组上的位置
        return;
    }
    int mid = l + r >> 1;
    Build(L[rt], l, mid);
    Build(R[rt], mid+1, r);
}

void Merge(int last, int &rt, int l, int r, int pos, int Fa)
{
    rt = ++cnt;
    L[rt] = L[last];
    R[rt] = R[last];
    if(l == r)
    {
        fa[rt] = Fa;
        de[rt] = de[last];
        return;
    }
    int mid = l + r >> 1;
    if(pos <= mid)
        Merge(L[last], L[rt], l, mid, pos, Fa);
    else
        Merge(R[last], R[rt], mid+1, r, pos, Fa);
}

void Update(int rt, int l, int r, int pos)
{
    if(l == r)
    {
        de[rt] += 1;
        return;
    }
    int mid = l + r >> 1;
    if(pos <= mid)
        Update(L[rt], l, mid, pos);
    else
        Update(R[rt], mid+1, r, pos);
}

///查找编号rt点在持久数组的下标
int Query(int rt, int l, int r, int pos)
{
    if(l == r)
        return rt;
    int mid = l + r >> 1;
    if(pos <= mid)
        return Query(L[rt], l, mid, pos);
    else
        return Query(R[rt], mid+1, r, pos);
}

int Find(int rt, int pos)
{
    int v = Query(rt, 1, N, pos);
    if(fa[v] == pos)///都是指下标的
        return v;
    return Find(rt, fa[v]);
}

int main()
{
    int opt, x, y, posx, posy;
    read(N);
    read(M);
    Build(root[0], 1, N);
    for(register int i = 1; i <= M; i += 1)
    {
        read(opt);
        read(x);
        if(opt == 1)
        {
            read(y);
            root[i] = root[i-1];
            posx = Find(root[i], x);
            posy = Find(root[i], y);
            if(fa[posx] != fa[posy])
            {
                if(de[posx] > de[posy])
                    swap(posx, posy);
                Merge(root[i-1], root[i], 1, N, fa[posx], fa[posy]);
                if(de[posx] == de[posy])
                    Update(root[i], 1, N, fa[posy]);
            }
        }
        else if(opt == 2)
            root[i] = root[x];
        else
        {
            read(y);
            root[i] = root[i-1];
            posx = Find(root[i], x);
            posy = Find(root[i], y);
            if(fa[posx] == fa[posy])
                putchar('1'), putchar('\n');
            else
                putchar('0'), putchar('\n');
        }
    }
    return 0;
}

洛谷测评,最慢测试点耗时(不开O2):264ms。

参考文献

  1. pengym. 题解 可持久化并查集. https://www.luogu.org/problemnew/solution/P3402, 2018-08-08

写在最后

因为题目真不难,所以博主偷偷懒就写了这么点,所以各位抱歉啦。不过我相信大家看通俗易懂的代码还是看得懂的吧(逃)。
感谢参考文献中提到的文献的帮助。
大多数内容为个人智力成果,如需转载,请注明出处,禁止作商业用途传播。
最后,感谢各位的阅读。

posted @ 2018-08-23 21:55  孤独·粲泽  阅读(221)  评论(0编辑  收藏  举报