12.2 省选训练总结2(2) LCT/可持久化

来源:互联网 发布:java linux 文件权限 编辑:程序博客网 时间:2024/06/06 04:44

目录

LCT

完成情况 题目 出处 分块AC Bounce 弹飞绵羊 BZOJ 2002 [HNOI2010] AC Cave 洞穴勘测 BZOJ 2049 [SDOI2008] 链剖AC 树的统计 Count BZOJ 1036 [ZJOI2008] Housewife Wind POJ 2763 魔法森林 BZOJ 3669 [NOI2014]

可持久化数据结构

完成情况 题目 出处 平衡树AC 普通平衡树 Tyvj 1728 郁闷的出纳员 BZOJ 1503 [NOI2004] Persistent Bookcase Codeforces Round #368 707D K-th Number POJ 2104 AC 花神的嘲讽计划Ⅰ BZOJ 3207 权限题 Couriers BZOJ 3524 [POI2014] AC Count on a tree BZOJ 2588 SPOJ 10628 权限题 谈笑风生 BZOJ 3653 权限题 简单题 BZOJ 2683 权限题 Peaks加强版 BZOJ 3551 [ONTAK2010] AC MEX-Query Codeforces Gym 101237A GERALD07加强版 BZOJ 3514 [Codechef MARCH14] AC 任务查询系统 BZOJ 3932 [CQOI2015] Gty的妹子序列 BZOJ 3744 可持久化并查集(&加强版) BZOJ 3673-3674 权限题 Dynamic Rankings BZOJ1901 ZJU2112 权限题 网络管理 Network BZOJ 1146 [CTSC2008] 权限题 最大异或和 BZOJ 3261 权限题 ALO BZOJ 3166 [HEOI2013] 权限题 XRQRS BZOJ 4546 Codechef

LCT

Splay对LCT就好像线段树对链剖。LCT是动态的!

Access操作,最重要的操作:把某点到根路径上的所有边设为Prefered Path,也就是挼(rua)到同一个Splay里面,而且令该点不再有Prefered Child。

所有路径只要Access端点后就好操作了。

Bounce 弹飞绵羊

这题我用分块早已水过……

然而是LCT模板题:只要维护操作link、cut和findroot(况且link还保证了其中一点是根)

Cave 洞穴勘测

维护一个动态并查集,支持加边,删边,维护两点连通性。

LCT维护时如何把某点设为根?

发现就是把这点到根的路径上的点的排列反过来。

反过来?Access这个点然后Reverse这棵Splay。(终于知道为什么洛谷模板要我写Reverse了)

树的统计 Count

链询问怎么做?只需要使某一端点转到根,另一端点Access即可提取出这段Splay。

要记住,区间修改都是打标记不是暴力修改。

Housewife Wind

给你这棵树边带权?

如果像链剖简单地下放至下面的节点处?动态的,一翻转就会改变值。

所以拆点,每条边拆成一个点两条边连回去。

魔法森林

给你一张图,每条边有a,b两个权值,让你找一条从st的路径,使得其上max{a}+max{b}最小。

先假设知道了一个a?那么做b的最小生成树以后做。

a是动态的,按a的大小来加边,加一条维护一下b的最小生成树,LCT来link和cut即可。

值域线段树

每个节点记录值域在[l,r]信息,最好动态开点。

如果值域过大,能离线就赶紧离散。

普通平衡树

可以值域线段树水过去。

排名和Kth是值域线段树的基本操作,加入删除也是。

前驱后继事实上完全可以先排名然后Kth。

郁闷的出纳员

维护数列支持:
加入一个数k
全部加上k
全部减去k(如果小于下限去掉这个数)
查询第k

然而只需要Lazy地记一下偏移量,插入时先消除偏移量影响。全部减少时再考虑一群直接删除。

可持久化

最关键的地方在于有的地方不需要暴力赋值,而是应该用之前的版本的“指针”。

Persistent Bookcase

维护(0,1)矩阵,支持:
单点置零/置一
i行取反
回到第K次操作前的状态

这个记录历史版本,每次都是暴力去复制一行,再开一个数组记历史维护的行是哪些。回退时复制之前某个数组即可。

可持久化值域线段树

可持久化的值域线段树,一般处理静态的序列值域问题。

末尾加上一个数的时候开一个新的值域线段树,只有log个点需要新建,其他的点直接用之前的节点。

要维护区间,得保证维护的值有可加/可减等运算性!

K-th Number

裸的模板!

花神的嘲讽计划Ⅰ

给你N,M,K,长度为N的串,M次询问,每次给你一个区间[x,y]和一个长度为K的串,问你询问串是否在区间[x,y]中出现过。

长度固定立马哈希,然后区间询问存在性,直接可持久化值域线段树。

AC后的感言:
千万千万千万要好好写哈希!!可以离线自然溢出再离散化!!千万不要像我一样用一般的哈希!!!我用MOD=1000109107的时候,一直WA,然后就乱改Base,改了什么733,373,23947,然后瞎改Base乱玩的时候选成23747突然就过了……

Couriers

区间询问众数。

由于是询问众数,并且只需关心是否超过一半个数,在这个区间的值域线段树上搜索时只需一直想大的区间走就行了。

Count on a tree

询问链K

在树上,建树时从父亲处复制过来,就可以得到这个点的前缀值域。一条链就是到lca的两个区间。

谈笑风生

a,k询问(b,c)组数,使ca,b的后代且a,b距离不超过k

这样只有abcbac两种。后者的贡献直接算子树大小再乘k,前者DFS建可持久化值域线段树,深度控制区间即可。

简单题

子矩阵加,子矩阵求和,不允许树套树的空间。

CDQ加树状数组可以水过,但是,可持久化值域线段树动态开一开点也可以过。

Peaks加强版

只经过边权不大于x的边,所能到达的点中点权的K小值是多少。强制在线。

Kruskal重构树来做,这棵树是每加一条边,加一个“边权”节点,把两棵树接上去。这样构出来,是棵二叉树,并且是个大根堆!边权的最大值变成了新树上两点的LCA的点权!

所以DFS序以后就变成了区间问题,可持久化值域线段树可维护。

MEX-Query

序列区间mex(钦点强制在线)。

mex的数学性质非常不好,要记其他东西来转。

可持久化值域线段树里每个值的位置存当前该值出现的最右位置,并且区间不记Sum,改记Min。不做可持久化值域线段树的区间加减。

往下时如果前缀区间最小值小于l了,说明这之中它没出现,就找这个区间。

MEX-Query
贴份超短代码:

#include <bits/stdc++.h>#define mid ((l + r) >> 1)using namespace std;const int maxn = 1000020;int cnt = 0, n, q, root[maxn], ls[maxn * 25], rs[maxn * 25], s[maxn * 25], u, v;inline void Insert(int p, int cop, int l, int r){    ls[p] = ls[cop]; rs[p] = rs[cop];    if (l == r){s[p] = v; return;}    u <= mid ? Insert(ls[p] = ++cnt, ls[cop], l, mid) : Insert(rs[p] = ++cnt, rs[cop], mid + 1, r);    s[p] = min(s[ls[p]], s[rs[p]]);}inline int Ask(int p, int l, int r){    return (l == r) ? l : (s[ls[p]] < u ? Ask(ls[p], l, mid) : Ask(rs[p], mid + 1, r));}int main(){    scanf("%d", &n);    for (v = 1; v <= n; v++){        scanf("%d", &u);        Insert(root[v] = ++cnt, root[v - 1], 0, maxn);    }    scanf("%d", &q);    while (q--){        scanf("%d%d", &u, &v);        printf("%d\n", Ask(root[v], 0, maxn));    }    return 0;}

GERALD07加强版

无向图多次询问保留图中编号在[l,r]的边的时候图中的联通块个数,强制在线。

LCT可以随便解决。

但是可以开一个数组,记录这条边可以替代的编号最大的边是谁。然后区间找的时候每有一个值小于l,说明它是有实效的,就1连通块。

任务查询系统

给出一些线段以及权值,询问覆盖了Xi的线段中,最小的Ki个值之和是多少。

建可持久化值域线段树时,有区间加入就加进来,有区间退出就删回去即可。

#include <bits/stdc++.h>#define mid ((l + r) >> 1)#define ll long longusing namespace std;const ll maxn = 100020;const ll maxp = 10000010;ll cnt = 0, n, q, root[maxn], ls[maxn * 120], rs[maxn * 120], c[maxn * 120], u, v, f, i, m;ll s[maxn * 120], las;inline void Insert(ll p, ll cop, ll l, ll r){    ls[p] = ls[cop]; rs[p] = rs[cop]; s[p] = s[cop]; c[p] = c[cop];    if (l == r){        s[p] += u * f;        c[p] += f;        return;    }    u <= mid ? Insert(ls[p] = ++cnt, ls[cop], l, mid) : Insert(rs[p] = ++cnt, rs[cop], mid + 1, r);    s[p] = s[ls[p]] + s[rs[p]];    c[p] = c[ls[p]] + c[rs[p]];}ll Query(ll p, ll l, ll r, ll k){    if (c[p] <= k) return s[p];    if (l == r) return k * l;    return k <= c[ls[p]] ? Query(ls[p], l, mid, k) : s[ls[p]] + Query(rs[p], mid + 1, r, k - c[ls[p]]);}vector <ll> A[maxn], D[maxn];int main(){    scanf("%lld%lld", &m, &n);    for (i = 1; i <= m; i++){        scanf("%lld%lld%lld", &u, &v, &f);        A[u].push_back(f);        D[v + 1].push_back(f);    }    for (v = 1; v <= n; v++){        root[v] = root[v - 1];        for (i = 0, f = 1; i < A[v].size(); i++){            u = A[v][i];            las = root[v];            Insert(root[v] = ++cnt, las, 0, maxp);        }        for (i = 0, f = -1; i < D[v].size(); i++){            u = D[v][i];            las = root[v];            Insert(root[v] = ++cnt, las, 0, maxp);        }    }    las = 1;    while (n--){        ll a, b, c;        scanf("%lld%lld%lld%lld", &u, &a, &b, &c);        printf("%lld\n", las = Query(root[u], 0, maxp, 1LL + (las * a + b) % c));    }    return 0;}

Gty的妹子序列

强制在线,多次询问区间逆序对数。

分块,先预处理块间的答案,然后对于零散部分再让它到中间块和以求出部分的可持久化值域线段树上去查。

可持久化并查集

只按秩合并,不路径压缩。每次在当前版本线段树做,有更新就退回以前的版本。

树状数组套值域线段树

用来做带修改的区间k小问题(但是显然可以用整体二分)。

每个节点维护自己的动态开点值域线段树,查的时候在所有需要的值域线段树上的值加起来。

网络管理 Network

单点修改,链上k小。

DFS序加树状数组,但是需要维护前缀,这时候树状数组套的才是真正的可持久化值域线段树。

可持久化Trie

Trie树可持久化的话,插入一个数也只会更改log个权值,所以又可以复制之前的数。这个本来也是二叉的,所以和可持久化值域线段树大致相同。

最大异或和

处理动态插入数和区间询问最大异或值的问题。

可持久化后可以获得区间Trie,在这上面跑即可。

ALO

求“区间内的次大值与区间内除次大值外的数的异或最大值”的最大值。

枚举次大值,可以获得两种可行区间,在这上面找异或最大即可。

XRQRS

动态加入删除,区间最大异或,区间k大。

这个同时维护可持久化Trie和可持久化值域线段树就行了,但是我们发现只是二分的方法不同,实质上01Trie就是一种值域线段树。可以不建可持久化值域线段树。

原创粉丝点击