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

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满足l-1\leq p \leq r-1使得s[p] xor s[n] xor x最大。
若只有p \leq r-1,那么就是可持久化Trie模板:将每个数进行二进制拆位,每次从字典树根节点出发贪心访问与当前位相反的边即可。
考虑l-1\leq p \leq r-1的情况,为了维护l-1\leq p,我们为每个节点增加一些信息,设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。
而因为对于每个询问root[r_{i}]root[l_{i}]对应值域区间相同,所以root[r_{i}]的值域区间的cnt值减去root[l_{i}-1]的值域区间的cnt值就是A[l_{i}...r_{i}]有多少个数落在值域区间内,即可持久化线段树中两个代表相同值域的节点具有可减性。
核心代码如下

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);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,192评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,858评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,517评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,148评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,162评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,905评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,537评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,439评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,956评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,083评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,218评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,899评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,565评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,093评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,201评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,539评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,215评论 2 358