前景

可持久化数组在许多地方有广泛的应用:可持久化并查集、支持回退操作的字符串、可持久化数据结构。

准备工作

我们需要对应的头文件和命名空间,这都是标准 c++支持的。

#include <ext/rope>
using namespace __gnu_cxx;

rope 的原理与基本操作

rope 是给予维护深度平衡的可持久化平衡树。每次操作都会新建节点,因此内部是可持久化的。
rope 一般用来维护一个支持历史版本查询的字符串。它本身也可以像字符串一样使用。

#include <ext/rope>
using namespace __gnu_cxx;
rope<char> str,x;
str.insert(pos,"abc")//在 pos 位置开始插入"abc"
str.push_back("x")//在末尾追加"x"
str.erase(a,b)//在 a 位置开始删除 b 个
str.replace(a,"abc")//在 a 位置开始用"abc"替换
str.substr(a,b)//返回从 a 位置开始共 b 个的字符串
str.reverse(a,b)//反转 [a,b) 区间,虽然很慢

rope 的可持久化

一般,我们可以利用 rope 底层可持久化的机制,进行 O(1) 回退。也就是我们只需要回退根节点的版本,我们就可以很顺利地访问当时的所有节点了。故回退复杂度为 O(1)
可持久化 rope 的初始化:

rope<char>*ver[100];
ver[0]=new rope<char>();

可持久化 rope 建立新版本和回退旧版本:

ver[i]=new rope<char>(*ver[i-1]);//从版本 (i-1) 建立新版本 i
ver[i]=ver[i-1]//版本 i 回退为版本 (i-1)

一道模板题 (Version Controlled IDE-UVa12538)

给定一个字符串,要求支持以下操作:
1 a str 在 a 位置插入 str
2 a b 删除 a 到 b 的字符串
3 a b c 输出 a 版本的 b 开始的 c 个字符
输入已经进行加密,请将每个数据的数字减去你已经输出的’c’ 的个数

#include <iostream>
#include <cstring>
#include <cstdio>
#include <ext/rope>

#define MX 1000001

using namespace std;
using namespace __gnu_cxx;

char str[MX];
rope<char> *rp[50001],tmp;
int ver[MX],num,n,d;

int main()
{
    rp[0]=new rope<char>();
    int op,a,b,c;
    scanf("%d",&n);
    for(register int i=1;i<=n;i++)
    {
        rp[i]=new rope<char>(*rp[i-1]);
        scanf("%d",&op);
        if(op==1)
        {
            ver[++num]=i;
            scanf("%d%s",&a,str);
            rp[i]->insert(a-d,str);
        }
        else if(op==2)
        {
            ver[++num]=i;
            scanf("%d%d",&a,&b);
            rp[i]->erase(a-1-d,b-d);
        }
        else
        {
            scanf("%d%d%d",&a,&b,&c);
            tmp=(rp[ver[a-d]]->substr(b-1-d,c-d));
            d+=count(tmp.begin(), tmp.end(), 'c');
            cout<<tmp<<endl;
        }
    }
    return 0;
}

又是一道模板题 (可持久化并查集-BZOJ3673)

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

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

#include <iostream>
#include <cstring>
#include <cstdio>
#include <ext/rope>

#define MX 20002

using namespace std;
using namespace __gnu_cxx;

rope<int> *fa[MX];
int src[MX];
int n,m;

int findfa(int x,int ver)
{
    if(fa[ver]->at(x)==x)return x;
    else
    {
        fa[ver]->replace(x,findfa(fa[ver]->at(x),ver));
        return fa[ver]->at(x);
    }
}

void comb(int a,int b,int ver)
{
    int f1,f2;
    f1=findfa(a,ver);
    f2=findfa(b,ver);
    fa[ver]->replace(f1,f2);
}

int same(int a,int b,int ver)
{
    int f1,f2;
    f1=findfa(a,ver);
    f2=findfa(b,ver);
    return (f1==f2);
}

int main()
{
    int a,b,o;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)src[i]=i;
    fa[0]=new rope<int>(src,src+n+1);
    for(int i=1;i<=m;i++)
    {
        fa[i]=new rope<int>(*fa[i-1]);
        scanf("%d",&o);
        if(o==1)
        {
            scanf("%d%d",&a,&b);
            comb(a,b,i);
        }
        else if(o==2)
        {
            scanf("%d",&a);
            fa[i]=fa[a];
        }
        else
        {
            scanf("%d%d",&a,&b);
            cout<<same(a,b,i)<<endl;
        }
    }
    return 0;
}
分类: 文章

1 条评论

konnyakuxzy · 2018年3月4日 1:13 上午

Orz Boshi
在 boshi 学完 rope 的一千年后的这个半夜蒟蒻 XZY 也学了 rope
然后发现一个奇怪的东西
就是您那个可持久化并查集里的那个 findfa 函数,最好写成这种形式:

int findfa(int x,int ver)
{
    if(fa[ver]->at(x)==x)return x;
    else
    {
        int tmp = findfa(fa[ver]->at(x),ver);
        if (tmp == fa[ver]->at(x)) return tmp;
        fa[ver]->replace(x, tmp);
        return tmp;
    }
}

究其原因是如果您直接就 replace 的话会导致空间的增加。
很多时候我们路径并没有被压缩,但是被访问了,这时候就没有必要 replace 浪费没用的空间。
比如您可以测测 BZOJ – 3674 那题,是此题的数据加强版
若不修改的话会爆空间 QvQ

回复 konnyakuxzy 取消回复

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用 * 标注