可持久化数据结构用来解决这个问题:
1.在某个版本的基础上修改,同时生成新的版本
2.查询某个版本的值。
直接复制版本十分浪费。
为了查询以前的信息,可以不动以前的节点,只建新节点。
这就是可持久化数据结构的思想。
可回退化数据结构比上面的更弱。它用来解决这个问题:
1.在当前版本上进行修改
2.回退一步
这个数据结构的要求比普通数据结构更强,但是比可持久化数据结构更弱。
可以使用一个栈,存储被修改的位置,回退的时候再撤销。
但是很多时候,不能直接这么做。
比如可回退化单调栈,上面这么做时间复杂度是错的。
因为每次插入以后可能删除很多元素。
可以使用二分。由于栈中的元素是单调的,可以二分到需要弹栈的位置,然后把栈顶设为这个位置。
这样子时间复杂度就是正确的。
此外,上面两个数据结构都需要保证无论输入数据怎么样,操作时间复杂度都是一个较低的值val。
可持久化数据结构有这么一个转化:
如果把这个点向生成它的版本连边,则形成一颗树。
这棵树从某节点到根经过的操作就是这个版本经过的修改操作。
dfs整颗树,在到一个节点时插入,离开一个节点的时候删除。
则可持久化问题转化为更弱的可回退化问题。
例题:
可持久化数组
使用可持久化线段树维护
可持久化并查集(没做)
使用可持久化数组。
并查集要只加按秩合并,复杂度才正确。
可持久化平衡树(没做)
codechef WEASELSC
由于题目的楼梯可递增/递减,所以可以把楼梯翻转后再做一遍。
通过观察,我们可以发现这两个引理:
1.所有楼梯的高度都在(a)里出现过。
2.设(l_i)表示(a)里(i)左边第一个(<a_i)的位置。
如果我们第i个位置选择了(a_i),则(l_i+1)到(i)都要选择(a_i)。
这是因为如果不选择(a_i),结果不会更好。楼梯数不会更少
有了这两个引理,我们显然可以设dp方程。
设(f_{k,i})表示有(k)个阶梯,目前dp到i。
则(f_{k,i}=max(f_{k-1,j}+(i-j)*a_j),j<i,a_j<a_i)
如果(j<i)且(a_j<a_i),则把(j)作为(i)的父亲。
问题转化为:
有一颗树,每个节点有一条直线,求出某个点(x)到根的所有直线截(x=k)的最大值。
这是个经典的问题。可以通过从根建可持久化李超树/有根树点分治等方法解决。
集训队互测 unknown
一道论文题。可以看论文。
动态半平面交
七彩树(没做,上面一道题弱化版)
异或粽子
如果我们要求全局最大xor和,则可以trie树。
这是个trie树经典问题。
如果要求区间最大xor和,把它差分成前缀最大xor和。
可以可持久化trie树。
每次最多会修改log个节点。
如果要求k大xor和,则根据经典套路可以二分/堆。
取k次最大值,每次拓展出可能成为解的位置即可。
购票
[NOI2018]你的名字
bzoj二分图
简单线段树分治。
需要可回退化数据结构
bzoj城市建设
显然可以线段树分治。
但是线段树分治需要支持动态最小生成树,支持回退。
使用lct,连边把环上权值最小的边删除,回退cut即可。
codechef FBCHEF
https://www.cnblogs.com/ctmlpfs/p/13677219.html
CF757G
CF1037H
一道比较简单的字符串数据结构题。
题目要求字典序严格(>)询问串且最小。
可以使用一个简单的贪心。解一定是贪心的让构造的串匹配最长长度+一个严格>原串该位置的最小字符。
考虑求出构造的串匹配的最长长度。这是个基本的sam+线段树合并/sam+可持久化线段树+dfs序匹配问题。
我们要知道是否有(pin [l,r])且(p-len+1in [l,r])其中len=匹配的长度。
合起来就是(pin [l+len-1,r])
设(nxt_i)表示(i)节点后的最小字符。没有设为(-1)
(nxt_i)可以通过求出构造的串匹配的最长长度的方法求出。
枚举(lcp)即可得到答案/判定无解。
CF464E(没做)
[IOI2015]分组(忘了)
HNOI2019 JOJO(忘了)
loj3225(没做)
jzoj4611(没做)
loj 网格图
jzoj5750
CF1063F
挖掘性质:
引理1:如果我们倒过来选,显然每次选比上次长度+1的最优。
如果不是这么选,则找到从字符串长度小到大第一个不符合这个条件的串。
然后通过从这个串的首/尾删除/插入字符。把这个串的长度变成上一个串的长度+1。
容易发现这样子依然合法。
设(f_i)表示(i)作为开头最多能选几个。
如果我们枚举一个长度(md),表示现在的(f_i)。
则要求对于([i+md,n])的区间存在一个(j)使得(lcp(i,j)>=md-1)且(lcp(i+1,j)>=md-1)
引理2:(md)可以二分。即如果存在以(i)为结尾的长度为(md)的解,也存在(i)为结尾的长度为(md-1)的解。
我们可以把所有解中,存在对应(dp_i)的最后一位的位的串拿掉,再删除重复的串的最上面一个。
容易知道,最上面的一个在原来如果长度为(le+1),则重复的串的最下面一个长度为(le)。
且最上面的一个重复串存在对应(dp_i)的最后一位的位。
所以可以删除。
所以可以二分(md)
然而数据范围达到了5e5,这样子肯定是不可取的。
引理3:(f_i-1<=f_{i+1})
如果把现在(f_i)的首位字符删除,则可以使用类似引理2的方法证明答案最多会小1。
移项得到(f_i<=f_{i+1}+1)
所以我们可以先把(f_i=f_{i+1}+1),然后不断令(f_i--),判定是否满足要求。
我们要求对于([i+md,n])的区间存在一个(j)使得(lcp(i,j)>=md-1)且(lcp(i+1,j)>=md-1)。
由于第一个条件的区间终点是(n),所以可以倒着建可持久化线段树。
第二/三个条件,满足条件的肯定在后缀数组上是连续的一段,可以二分+st表求出。
查询这两个区间最大值是否(>=md-1)即可。
时间复杂度(nlog_2n)
loj6198
比较简单的字符串数据结构题。
有两种做法:
1.sa。
根据经典结论,两个字符串的lcp等于它在sa上的rmq。
考虑分治。每次取出最值分治。
这样子好处在于lcp是一定的。
接下来我们要求一个串对一个区间串的xor和的最大值。
这是个经典的问题。可以trie树解决。
但是直接分治时间复杂度是错误的。根据套路可以使用较小的xor较大的区间贡献答案。
根据启发式合并/轻重链剖分的复杂度,这样子时间复杂度是正确的
2.sam
根据经典结论,两个字符串的lcp等于它在sam parent树上的lca深度。
建立反串的sam。
dfs parent树。
我们要统计两个trie树的贡献。
这可以对trie树进行dfs。
当dfs到某个节点x时,枚举当前节点的第一个trie树选什么,根据大小关系得知它和哪个节点xor。
做完后把trie树合并到根即可。
(其实线段树分治,回滚莫队都需要可回退化数据结构,并且要求时间复杂度严格正确)