「数据结构进阶」例题之可持久化数据结构

0x40「数据结构进阶」例题

可持久化数据结构能够维护数据集的历史状态,其核心思想在于仅仅维护数据集改变的量,这样其时间复杂度不会改变,空间复杂度增长仅为与时间同级的规模。

可持久化Trie

向可持久化Trie依次插入cat,rat,cab,fry的过程如下。


可持久化Trie

我们发现,每次从第i个根节点开始遍历,就会访问到前i个字符串。


可持久化Trie

例题

BZOJ 3261
题意:非负整数序列a,长度n,m个操作共两类。第一类操作A x在序列末尾插入一个数x,序列长度+1。第二类操作P l r x,要求找到l<=p<=r,使得a[p] xor a[p+1] xor … xor a[n] xor x最大,求最大值。n,m<=3e5
由异或的前缀和性质,我们设s[i]表示a序列的前i个数异或起来的结果,那么a[p] xor a[p+1] xor … xor a[q]=s[q] xor s[p-1]。因此,我们转化为求一个p满足使得s[p] xor s[n] xor x最大。
若只有,那么就是可持久化Trie模板:将每个数进行二进制拆位,每次从字典树根节点出发贪心访问与当前位相反的边即可。
考虑的情况,为了维护,我们为每个节点增加一些信息,设end[x]表示x节点时第几个二进制数的末尾节点。lastst[x]表示以x为根的子树中end的最大值,每次只需要考虑lastst值大于等于l-1的节点即可。
代码如下

void insert(int i, int k, int p, int q) {
    if (k < 0) {
        latest[q] = i;
        return;
    }
    int c = s[i] >> k & 1;
    if (p) trie[q][c ^ 1] = trie[p][c ^ 1];
    trie[q][c] = ++tot;
    insert(i, k - 1, trie[p][c], trie[q][c]);
    latest[q] = max(latest[trie[q][0]], latest[trie[q][1]]);
}
int ask(int now, int val, int k, int limit) {
    if (k < 0) return s[latest[now]] ^ val;
    int c = val >> k & 1;
    if (latest[trie[now][c ^ 1]] >= limit)
        return ask(trie[now][c ^ 1], val, k - 1, limit);
    else
        return ask(trie[now][c], val, k - 1, limit);
}
int main() {
    cin >> n >> m;
    latest[0] = -1;
    root[0] = ++tot;
    insert(0, 23, 0, root[0]);
    for (int i = 1; i <= n; i++) {
        int x; scanf("%d", &x);
        s[i] = s[i - 1] ^ x;
        root[i] = ++tot;
        insert(i, 23, root[i - 1], root[i]);
    }
    for (int i = 1; i <= m; i++) {
        char op[2]; scanf("%s", op);
        if (op[0] == 'A') {
            int x; scanf("%d", &x);
            root[++n] = ++tot;
            s[n] = s[n - 1] ^ x;
            insert(n, 23, root[n - 1], root[n]);
        }
        else {
            int l, r, x; scanf("%d%d%d", &l, &r, &x);
            printf("%d\n", ask(root[r - 1], x ^ s[n], 23, l - 1));
        }
    }
}

可持久化线段树

可持久化线段树

显然,可持久化线段树与可持久化Trie实现机理相同,这里不做赘述。
PS:可持久化线段树难以完成区间修改,因为延迟标记不可用。解决方法是用有局限性的标记永久化操作(例题 HDOJ 4348)
类型定义

struct SegmentTree {
    int lc, rc; 
    int dat;
} tree[MAX_MLOGN];
int tot, root[MAX_M]; 
int n, a[MAX_N];

建树

int build(int l, int r) {
    int p = ++tot;
    if (l == r) { tree[p].dat = a[l]; return p; }
    int mid = (l + r) >> 1;
    tree[p].lc = build(l, mid);
    tree[p].rc = build(mid + 1, r);
    tree[p].dat = max(tree[tree[p].lc].dat, tree[tree[p].rc].dat);
    return p;
}
//main()中
root[0] = build(1, n);

插入节点

int insert(int now, int l, int r, int x, int val) {
    int p = ++tot;
    tree[p] = tree[now]; 
    if (l == r) {
        tree[p].dat = val; 
        return p;
    }
    int mid = (l + r) >> 1;
    if (x <= mid) 
tree[p].lc = insert(tree[now].lc, l, mid, x, val);
    else 
tree[p].rc = insert(tree[now].rc, mid + 1, r, x, val);
    tree[p].dat = max(tree[tree[p].lc].dat, tree[tree[p].rc].dat);
    return p;
}
//main()中
root[i] = insert(root[i - 1], 1, n, x, val);

例题

POJ2104 K-th Number
本题除了可持久化线段树做法外,还有归并树,整体二分,线段树套平衡树等做法,这里主要介绍可持久化线段树做法。
离散化A序列,值域为[1,T]。
在[1,T]上建立可持久化线段树(在值域上建立),每个节点保留一个cnt值表示节点对应值域区间[L,R]中有多少个数,初始值为0。
对于每个A[i],则在A[i]离散化后对应的节点完成单点修改,令其cnt+1。
而因为对于每个询问和对应值域区间相同,所以的值域区间的cnt值减去的值域区间的cnt值就是有多少个数落在值域区间内,即可持久化线段树中两个代表相同值域的节点具有可减性。
核心代码如下

int ask(int p, int q, int l, int r, int k) {
    if (l == r) return l; 
    int mid = (l + r) >> 1;
    int lcnt = tree[tree[p].lc].cnt - tree[tree[q].lc].cnt;
    if (k <= lcnt) return ask(tree[p].lc, tree[q].lc, l, mid, k);
    else return ask(tree[p].rc, tree[q].rc, mid + 1, r, k - lcnt);
}
//main()中
int ans = ask(root[ri], root[li - 1], 1, t, ki);

你可能感兴趣的:(「数据结构进阶」例题之可持久化数据结构)