Trick

1.异或问题在 trie 树上的常用处理方式

第一种 trie:从高位到低位建出,维护大小信息。

CF1983F.array-value

首先二分,然后要求权值 \(\le mid\) 的区间的数量。然后如果区间 \([l,r]\) 合法,那么 \([l-1,r],[l-2,r],\dots,\) 一定也合法,因为都包含了合法的对。因此我们对于每个 \(r\),找到最大的 \(l\) 使得区间 \([l,r]\) 合法即可。

把所有数插入 trie,枚举右端点 \(r\),把 \(a_r\)\(mid\) 都拉到 trie 上,维护一条链,这条链上的数都与 \(mid\) 相同。假设现在要处理第 \(x\) 位,\(a_r\) 这一位为 \(c_1\)\(mid\) 这一位为 \(c_2\)

如果 \(c_2=0\),为保证不超过 \(mid\),异或值的这一位必须是 \(0\),因此走向当前点 \(c_1\) 方向的儿子;如果 \(c_2=1\),那么异或值这一位如果为 \(0\),后面怎样都是合法的,而这一位如果为 \(1\),依然紧贴 \(mid\) 限制。因此用 \(c_1\) 方向的子树更新答案,再走 \(c_1 \oplus 1\) 方向的子树即可。

因为也包含了异或值与 \(mid\) 相等的情况,所以最后还要用最终所在的位置的信息更新答案。

这里空节点与根的意义不同,因此建议从 \(1\) 开始给节点标号。

这道题的解法就是 Trick 1:在从高到低建出的 01-trie 上用两个数确定方向,每次只走紧贴上界的一边,另一边通过预处理得到

[十二省联考 2019] 异或粽子

\(k\) 比较小,考虑用堆维护贪心,初始时对于每个 \(r\) 维护和它对应的 \(l\),然后考虑取出一个最优解后的扩展。

做法一:

对于一个右端点,它有若干个合法的左端点,它们的取值是一段区间。每次取出一个解后,相当于对于右端点禁用了左端点这个位置,那么 \(r\) 合法的左端点区间就被划分成了两段,这样进行下去即可。

问题在于找到这个最优解的位置。建出来可持久化 trie。考虑可持久化的另一个套路:Trick 2:可持久化右端点,记录最大的左端点。那么记 \(S_i\) 表示节点 \(i\) 是哪个数的末尾节点,如果不是末尾结点则表示子树最大值,然后能走 \(p\) 的条件就是 \(S_p \ge l\),最后返回走到的末尾结点的 \(S_p\) 即可。

做法二:

考虑对每个右端点初始时要的是最大值,接下来就是非严格次大值,依次下去,而找一个数与一段区间上的数异或的 \(k\) 小值容易在 trie 上二分得出,这个二分就类似线段树上二分。

Trick 3:因为异或有交换律,先把 \(k\) 翻倍方便计算,因为 \(x \oplus x=0\),所以不用担心自己异或自己的情况。然后直接用普通 trie 即可。

这是一个 \(\mathcal{O}(k \log V)\) 的做法,如果 \(k\) 很大,我们有一个 \(\mathcal{O}(n \log ^2V)\) 的做法,也就是:

CF241B.Friends

我们还是想独立考虑每个数。如果想要给每个数一条确定的在 trie 上的走向,要么是给一个 \(k\) 要求它的异或前 \(k\) 大,要么是给一个 \(x\) 求与它异或 \(\ge x\) 的数之和。我们并不知道应该给某个数分配这 \(k\) 大中的多少,因此考虑确定 \(x\),也就是第 \(k\) 大的数。

这个很好求,先二分一个 \(mid\),然后求有多少个数的异或 \(\le mid\),方法即是 Trick 1。找到后对于每个数与它异或求 \(>x\) 的数之和,依然在 trie 上二分,这里在第 \(i\) 位走整个子树的时候,异或出来的值应该是 \(mid\) 从高到低的 \(i\) 位与在子树中的每个数与 \(a_x\) 异或的 \(i-1\) 位拼起来,因为前面 \(i\) 位是紧贴 \(mid\) 的。重点是后面怎么求。

Trick 4:排序后 trie 上一个子树中的数是一段连续的区间。因此记录左右端点,拆位前缀和即可。

感性理解一下,在一个子树中说明有相同的 LCP,因此大小关系(在这里也就是字典序)肯定是比较接近的那些数。

类似的结论是:Trick 4':对于 \(a<b<c,\min(a \oplus b,b \oplus c) \le a \oplus c\),也就是说,异或的最小值只会在排序后相邻的数间取得。

第二种 trie:从低位到高位建出,维护异或和信息,支持较复杂修改。

[省选联考 2020 A 卷] 树

考虑每个节点开一个 trie 维护,这个 trie 要维护异或和,每次更新答案就是把它的子树合并起来再整体加一。就是一个跟线段树合并类似的过程。

所以你的 trie 要支持:插入,合并,整体加一,维护异或和。

Trick 5:从低到高建出 trie 树,然后考虑向上更新信息。

  • 插入:只需从低到高位插即可,其他的与普通 trie 无异。为了防止进位,可以全都插到可能达到的最大深度。

  • 维护异或和:这里我们采用类似线段树的 pushup。对于它的 \(0\) 儿子,由于下面是高位,现在的位置是低位,所以下面的异或和要先右移,然后再加上这一位的数。当这一位是 \(1\) 的时候,注意落在这个点 \(1\) 子树的数的数量必须是奇数,否则异或就会帮你消掉,这位的贡献还是 \(0\)

  • 整体加一:考虑一个数加一对其二进制的变化,是把一段后缀 \(1\) 全都变成 \(0\),然后它前面的第一个 \(0\) 变成 \(1\)。考虑在 trie 上描述这个过程:就是每个点的 \(0,1\) 儿子互换,然后对于这一位原来是 \(0\) 的操作到这里就结束了,对于这一位是 \(1\) 的还要继续操作。但是交换了,所以我们往 \(0\) 子树上走即可。回来的路上再 pushup 即可。

  • 合并:类似线段树合并,设计函数 int merge(p,q) 表示合并 \(p,q\) 两个点的子树得到的根,然后因为没有边界判断,所以直接把每个点的信息全在 \(p\) 上做更新即可。不建议进行 pushup,因为 pushup 不进行叶节点的特判会出错,线段树合并时在叶节点直接返回便没有这样的问题。

[Ynoi2010] Fusion tree

\(1\) 邻域转化为儿子和父亲,每个点开个 trie 维护儿子操作,打 tag 维护父亲,trie 的操作被上个题严格包含。

CF778C.Peterson Polyglot

考虑删掉一层,那么应该将这一层上面的点的所有儿子合并成一个,在合并的时候新建节点,统计这个数量就是减少的点数,原因就是原来这两个点都是存在的,现在合成一个了。具体来说:边直接被我们删掉,边下面的点被合成一个当做根。

这里有 trie 合并的一些细节问题:合并的复杂度取决于两个子树大小的较小值,因此和启发式合并复杂度一致。只会新建较小值个点也就是 \(\mathcal{O}(n)\) 个点。

【MX-X6-T6】機械生命体

Trick 6:打 tag。

从低到高建出 trie 数后,用 \(cnt\) 维护经过每个节点的数量即可解决操作 \(1,2,4\)

对于操作 \(3\),考虑合法的部分在 trie 上体现为一个子树。那么先考虑全局的情况,这个是好维护的,对每个点开一个 \(tag\) 表示这个位置要加 \(tag\),pushdown 类似于高精度一样,进一位所以 \(tag\) 要除二,如果 \(tag\) 是奇数还要交换 \(01\) 儿子并给原先的 \(1\) 儿子的 \(tag\) 加一。

比较麻烦的是现在在某个子树上操作,但并不是只操作子树,加上 \(v\) 后除了子树内部结构会变,这整颗子树也会移动到另一个地方,肯定要用 trie 合并,但直接做还是有点麻烦,考虑新建出来一整条链,把子树挂在链底,在这条链的根上打 \(tag\) 后合并链的根和原先的根,这样依次只会建出 \(\log V\) 个节点,复杂度正确。

练习:CF1515H.

CF1665E.MinimizOR

Trick 7:值域在 \([0,2^k)\) 的数两两 OR 的最小值是前 \(k+1\) 小的数两两 OR 的最小值。类似地,值域在 \([0,2^k)\) 的数两两 AND 的最大值是前 \(k+1\) 大的数两两 AND 的最大值。

证明是相似的,只讨论 OR 的情况。考虑归纳。首先 \(k=1\) 时成立。

  • 若第 \(k\) 位有 \(\ge 2\)\(0\),那么答案这一位是 \(0\),选的数这位需要是 \(0\),那么肯定是较小的那些数;同时后面的部分要尽量小,那么保留前 \((k-1)+1\) 小的数就是对的,再加一个数也没有问题。

  • 若第 \(k\) 位全是 \(1\),答案这一位也是 \(1\),这一位对数大小关系没有影响,对答案也没有影响,直接删掉,保留前 \(k\) 小也是对的。

  • 若第 \(k\) 位有一个 \(0\),答案这一位是 \(1\),那么把这位为 \(0\) 的那个数改 \(1\) 不会影响答案,但是会影响大小关系。如果不放这个数则是前 \(k+1\) 小,因为是去掉它后的前 \(k\) 小,它才是最小的数;如果放这个数要最小化的也是后面,直接前 \(k\) 小就是对的。

更舒服的证明可以看 CF1665E 题解区

2.曼哈顿距离和切比雪夫距离的相互转化

假设平面直角坐标系上有两个点 \((x_1,y_1),(x_2,y_2)\)

你可以向上,下,左,右走一格,那么这两点间的距离就是曼哈顿距离,它的定义为 \(|x_1-x_2|+|y_1-y_2|\)

如果你还可以向左上,左下,右上,右下走,那么这两点间的定义就是切比雪夫距离了,它的定义为 \(\max(|x_1-x_2|,|y_1-y_2|)\)

考虑画出与远点曼哈顿距离为 \(1\) 的点,发现构成了一个斜放的正方形框架,边长为 \(\sqrt 2\);再画出切比雪夫距离为 \(1\) 的点,是一个以 \((0,0)\) 为中心的正放的正方形,边长为 \(2\)

考虑转化二者,从图上来看,两个距离只是简单地旋转和放缩,旋转 \(45°\) 即是从 \((x,y)\) 变为 \((x+y,x-y)\),但是这样会带来 \(\sqrt 2\) 倍的放大。

对于曼哈顿转切比雪夫,这正是我们想要的,因此原坐标系中的曼哈顿距离等价于做了 \((x+y,x-y)\) 变换的坐标系中的切比雪夫距离。

对于切比雪夫转曼哈顿,它最终比曼哈顿距离的范围扩大了 \(2\) 倍,因此原坐标系中的切比雪夫距离等价于做了 \((\frac{x+y}{2},\frac{x-y}{2})\) 变换的坐标系中的曼哈顿距离。

当然,也可以使用另一个经典的 Trick:|a-b|=\max(a-b,b-a) 来推导坐标系的变换,具体可见 this

两者的优劣

二维的问题比较困难,我们通常的想法是对两维分别计算。

切比雪夫距离只与两维距离的较大值有关,可以理解为它是独立的,在数点问题上有着更好的性质。

曼哈顿距离是一个加和的形式,因此对于求和类的问题更好处理,其实就是在求和上独立。

例题:

[ABC233Ex] Manhattan Christmas Tree

原先的距离与两维都有关系,转成切比雪夫后就是一个正矩形,然后二分一个距离,主席树查矩形内点的数量即可。

[ABC351E] Jump Distance Sum

看起来是要对魔改了的切比雪夫距离求和,观察一下就发现行走不改变 \(x+y\) 的奇偶性,因此只有奇偶性相同的点才能互相到达。

分开处理,还是先转成曼哈顿距离,这里有一个问题就是 \(x+y\) 为奇数是会下去整,这里我们要求和所以统一平移一下没有关系,当然也有另一种做法,在后文有说。

转成曼哈顿距离就随便做,两维独立,分开求和。现在要统计形如 \(\sum_{1 \le i<j \le n}|a_i-a_j|\) 状物,对所有对求,所以可以排序,然后记个 \(S\) 表示前面的和即可。

[IOI2007] pairs 动物对数

\(B=1\) 枚举左边界二分右边界即可。

\(B=2\) 曼哈顿距离转切比雪夫距离,排序后对第一维扫描线,树状数组维护第二维即可,避免出现负数可以都加 \(M=75000\)

\(B=3\) 我的歪解是枚举两维的值,对第三维做前缀和,时间复杂度 \(\mathcal{O}(nM^2)\),总共跑了 \(12\) 秒。

有结论:\(n\) 维曼哈顿距离可以转成 \(2^{n-1}\) 维的切比雪夫距离。实质上是考虑曼哈顿距离中每个绝对值内部的符号,因此每增加一维会多两种符号。若原先的点坐标为 \((x_1,x_2,\dots,x_n)\),则可以转化为 \(x_1\sum_{i=2}^n (\pm x_i)\),每种可能的符号都要取到,那么转成四维切比雪夫距离,对一维做扫描线,树状数组维护另外三维即可,时间复杂度 \(\mathcal{O}(n\log^3M)\)

[POI2006] MAG-Warehouse

这就是上文的第二种处理方式。首先是距离和,因此曼哈顿距离比切比雪夫距离有更好的性质。那么考虑把切比雪夫距离转成曼哈顿距离,这里先不管奇数的情况,直接除二。

然后两维互不相关,想要和最小,显然 \(x,y\) 都应该取对应维度的中位数,注意这里的中位数是点的数量的中位数。再转回原坐标系,有 \(85\) 分。

如果有除二,为避免复杂地分类讨论,可以直接让 \(x\) 左右扩展两格,\(y\) 同理,算一遍,即可。开 unsigned long long。

[USACO08OPEN] Cow Neighborhoods G

寻找有用点对。

这道题的重点并不在于转化距离,当然还是要从曼哈顿转成切比雪夫。然后用并查集维护。

但是一个点可能与很多点连边,直接维护复杂度难以承受。考虑一维是怎么做,即一个点可以向 \([x-d,x+d]\) 中的点连边,问你连通块数,显然直接连前驱后继即可,因为只关心连通性,所以 \(a \to c,a \to b\)\(a \to c \to b\) 是一样的,那么我们只找距离每个点最近的点合并就能串联起来。

二维也是同理,排序后从小到大扫 \(x\),在合法范围内找前驱后继连边即可。这里如果和前驱连不了那么和其它的点更是连不了,因为前驱的 \(x\) 在当前点的前面,能连的点 \(x\) 可以更小,所以这样不会遗漏情况。

Four Coloring

之前的题目利用的都是曼哈顿距离和切比雪夫距离在操作上的性质,这道题则比较巧妙,利用的是切比雪夫距离的形状。

曼哈顿距离为 \(d\) 的点看起来不太能做,转成切比雪夫后对于一个大小为 \(d\) 的矩形,里面都没有距离为 \(d\) 的点,直接染成相同颜色因此考虑以 \(d\) 为长度对行和列分块。那么每块间都有距离为 \(d\) 的点,都要不同色。现在问题转化为,给你一个网格,你需要对它四染色使得与每个方格八联通的方格都不同色。

这个问题是比较经典,考虑按 \(i\)\(j\) 的四种奇偶性染四种颜色,那么容易证明这样做合法。

3.区间 dp

区间 dp 好像还有一部分是只需考虑边界转移而做到 \(\mathcal{O}(n^2)\) 的,但是最基本的形式就是以下两种:

  • 形式一:\(f_{l,k},f_{k+1,r} \to f_{l,r}\),然后再考虑左右端点处的边界情况。

  • 形式二:考虑第一个满足某条件的东西或者说极大区间 \([l,k]\),然后和后面的 \([k+1,r]\) 转移,这种形式在计数问题中常见,为了防止算重。

CF1132F.Clear the String

本题为形式二。考虑 \(s_l\) 是怎么消掉的:要么它是自己嗯删的,然后剩下 \([l+1,r]\),就是 \(f_{l,r}\leftarrow f_{l+1,r}+1\);要么它与另外的一些字符一起删掉了。但是 dp 肯定不会考虑完整的状态,所以我们不应考虑一串相等的,而只应考虑一对相等的。枚举 \(k \in(l,r]\),若 \(s_k=s_l\),它们可以一起消掉。留下 \([l+1,k-1]\)\([k+1,r]\),也就是 \(f_{l,r}\leftarrow f_{l+1,k-1}+f_{k+1,r}+1\)。但这样也不对,这样你就真的是只考虑一对对的消除了,假如 \([l,k]\) 全都是一个字符,那是可以一次消掉的,但是这样会算好多次才消掉。可以考虑把 \(s_l\) “附赠” 到 \([k,r]\) 上去,当我们把 \([k,r]\) 删掉是必然有一次删 \(s_k\),这个时候 \(s_l\) 跟过去即可。这种东西区间 dp 里还挺常见的,就是,有的时候整个代价是不方便直接算的,考虑把两部分合并到一个状态里。在本题中就是你肯定不能武断地直接把次数加一,因此考虑把左边这个字符的删除并到右边。

但是这样做为什么是对的呢?也就是,为什么这样能考虑到所有的删除策略:考虑一段字符 \(\texttt{aaabbb}\),它虽然不会一步考虑到 \(\texttt{aaa}\) 一起删,但是它把 \(\texttt{abbb}\) 删掉后一次一次往左边添加 \(\texttt{a}\) 也是一样的效果。然后大体上感觉一下他就是对的了。

当然这道题也可以用形式一做,只需要考虑端点处的转移也就是如果 \(s_l,s_r\) 相等,可以一次消掉,就可以从 \(f_{l+1,r}\)\(f_{l,r+1}\) 转移,为什么不是 \(f_{l+1,r-1}+1\) 原因同上。

P4170 [CQOI2007] 涂色

和上个题差不多,用形式一,考虑端点处的转移,如果 \(s_l=s_r\) 就可以刷一次搞定再做后面的,所以可以从 \(f_{l+1,r}\)\(f_{l,r+1}\) 转移。

[ABC217F] Make Pair

形式二。设 \(f_{l,r}\) 表示 \([l,r]\) 的方案数。

考虑 \(l\) 要和 \(k\) 走,那么 \(f_{l,r}\leftarrow f_{l+1,k-1} \times f_{k+1,r}\),顺序重要那么还要考虑人们走的顺序,总共会走 \(\frac{r-l+1}{2}\) 次,右边走了 \(\frac{r-k}{2}\) 次,左右两部分的离开时并列的,因此直接从这些步数中选出那么多给右边就行了。更仔细的说,\([l,k]\)\([k+1,r]\) 同级,两边可以用组合数算,内部顺序确定,比如左边必须先做 \([l+1,k-1]\) 在做 \((l,k)\)

初值,大概是 \(f_{i+1,i}=1\),我们会进行所有合法的转移,所以只需保证空区间不会对答案造成影响即可,而乘法转移的单位元是 \(1\)

4.冒泡排序

考虑冒泡排序一轮的本质是什么,一个数会被交换到第一个大于它的数的前面,然后这个大于它的数也会交换到大于这个大于它的数的前面,依此类推。

考虑简单维护这个过程,我们可以记 \(c_i\) 表示 \(\sum_{j=1}^{i-1}[a_j>a_i]\),然后每一轮的变化就是 \(\max(c_{i+1}-1,0)\to c_i\),就是看一下上面那个过程,每没有在这个交换过程中的数都会往前平移一格,同时 \(c\) 值也减少了一,因为有一个比他大的数被交换到了后面。而对于那些被交换的,\(c\) 已经是 \(0\) 了。

如果给定的是一个排列,或者说,值域没有重复,我们更是可以直接用 \(c_i\) 表示数值 \(i\) 出现位置前面有多少个数比它大,那么交换一轮就是 \(\max(c_i-1,0)\to c_i\),证明与上面类似,而形式更加方便处理了。

这两种 \(c_i\) 都可以进行一些冒泡排序基本信息的维护,例如,交换次数即逆序对数是 \(\sum c_i\),交换轮次是 \(\max c_i\)

[NOI Online #1 提高组] 冒泡排序

\(c_i\) 表示数值 \(i\) 的前面有多少个数比它大,那么逆序对数是 \(\sum c_i\)

询问是简单的,答案是 \(\sum \max(c_i-k,0)\),分成 \(c_i > k\)\(c_i \le k\),树状数组维护 \(c_i\ge k\) 的数和数量即可。

修改只会影响 \(\mathcal{O}(1)\) 个位置,交换 \(p_x,p_{x+1}\),那么 \(x+1\) 后面的和 \(x\) 前面的位置都不收影响,只考虑 \(c_{p_x}\)\(c_{p_{x+1}}\) 即可。

  • \(p_x<p_{x+1}\),交换后比 \(p_{x+1}\) 大的数没变,比 \(p_x\) 大的数多了一个。

  • \(p_x>p_{x+1}\),交换后比 \(p_{x+1}\) 大的数少了一个,比 \(p_x\) 大的数没变。

[JOI Open 2018] 冒泡排序 2

这里 \(a\) 不是排列,但是还是可以记 \(c_i\) 表示数值 \(i\) 前面有多少数比它大,答案是 \(\max c_i\),为使 \(c_i\) 尽量大,\(i\) 应取最后一次出现。

每个数最后一次出现也不一定有贡献,考虑一个数,如果它后面有数比它小,那么这个数就完全不比他后面这个数好了,因此,有用的位置是后面所有数都比它大。对于这样的位置,假设是 \(a_x=y\),那么贡献就是 \(\sum[a_i>y]-(n-x)\),然后你发现不满足条件也没关系,因为一个数不满足条件后面减去的就多了,则上式的值肯定不如满足条件的大,所以只需要维护 \(\sum[a_i>y]+x\) 的最大值。

对于一个修改 \(a_x=y\),值 \(a_x,y\) 处的答案会变化,这个直接算,需要知道某个值出现位置的最大值,用 set 维护。同时还有 \([a_i>y]\),一个数 \(y\) 的影响是 \([1,y)\) 这个区间加一,处理一下贡献的增删即可。

直接动态开点线段树空间开不下,要离散化一下。

posted @ 2024-10-28 17:53  aCssen  阅读(80)  评论(0)    收藏  举报