异或问题合集

评价:我有异或症。

收集了一些跟异或相关的 trick,主要是以题目的形式放出来,做到了新的会再加。

由于本人不太会线性基,正在考虑要不要改成 01-Trie 学习笔记,相关题目暂时应该是没有,主要考虑异或本身的性质。

P4551 最长异或路径

题目链接

disu 表示根到 u 路径上所有边权的异或和,众所周知异或有个很好的性质就是 aa=0,那么根据这点我们不难发现:u,v 路径上的边权异或和就等于 disudisv,因为 LCA 向上的边权都异或两次消去了。

现在的问题就变成:找出最大的一个点对 (u,v),使得 disudisv 最大。

将所有 disi 从高位到低位插入到 Trie 中,然后枚举 disu,贪心地从 Trie 上向下走找到最大的 disudisv 即可。具体来说就是每次看看能不能朝相反的那一位走,因为是从高位到低位插入这样一定是最优的。这个是非常基础的,相信大家都会。

时间复杂度 O(nlogV)

P9745 树上异或

题目链接

插播一个 dp 题。

朴素的想法是设 fu,i 表示 u 所在的连通块权值为 i 时(只考虑子树内),子树内其他连通块的贡献之和,gu 表示 u 所在子树的答案。

fu,i 的转移就考虑每个儿子到自己的边断不断:

fu,ifu,i×gv+jfu,j×fv,ij

gu 其实就求个和:

gu=xx×fu,x

时间复杂度 O(nV),肯定过不去。

异或的一个很好的性质就是运算过程中每一位之间都是独立的,不妨按位考虑,也就是拆位。

fu,i,0/1 表示 u 所在连通块权值的第 i 位为 0/1 时(同样也只考虑子树内),子树内其他连通块的贡献之和,gu 表示 u 所在子树的答案。

转移其实大差不差:

fu,i,0fu,i,0×gv+fu,i,0×fv,i,0+fu,i,1×fv,i,1fu,i,1fu,i,1×gv+fu,i,0×fv,i,1+fi,i,1×fv,i,0

哦,还有 gu 改成这样:

gu=i2i×fu,i,1

时间复杂度 O(nlogV)

P5283 异或粽子

题目链接

ai 做一个异或前缀和,也就是记 si=j=1iai,那么区间 [l,r] 的异或和就可以转化为 srsl1,特别规定 s0=0 即可。

于是问题就转化成了:给你一个序列 s,求前 k 大的两两异或值之和。

注意这里要求 sisj 时有 i<j,这是比较烦人的,但我们可以直接将 i<j 的限制去掉,转而求前 2k 大的两两异或值,然后再将答案除以 2 就好了,因为发现这样只是把 i,jj,i 重复算了一遍(由于有 sisi=0i=j 的情况不需要任何特殊处理)。

事实上强制带 i<j 的限制也可以写可持久化 Trie 来解决。

考虑给定其中一个 si 时,能不能求出来第 k 大的 sisj。其实是简单的,Trie 树上每个点维护子树内有几个数,把 si 扔到 Trie 上去进行一个类似于线段树上二分的过程就好了,当一位填 1 后最小排名不会超过 k 时就贪心填 1

然后类比一下超级钢琴的做法,开一个小根堆,对于每个 si 先将最小的 sisj 扔进堆里,这样就能保证堆顶一定是当前能取的最小值。接下来每次取出堆顶后,假如这个堆顶对它的 si 来说是第 t 小的,那就用上面方法把第 t+1 小的扔进去,这样重复 2k 次之后我们就成功求出前 2k 大的两两异或值了。

于是就做完了,时间复杂度 O((n+k)logV)

CF241B Friends

题目链接

乍一看这不就是异或粽子吗?但是发现 k 非常大,而上面做法的复杂度是带个 k 的,过不去。

其实我们还有一个跟 k 无关的 O(nlog2V) 做法。

首先还是先把 k 乘上 2,这些就不多说了。然后我们分为两部分去完成:求出第 k 大的异或值 kth,求出大于等于 kth 的异或值之和。

对于第一部分,考虑二分答案 mid,判断 kth 是否小于等于 mid。不难发现此时我们只需要求出有多少对 (i,j) 满足 aiaj>=mid 就行了,可以直接把每个 ai 扔到 Trie 树上,类似线段树二分的过程(或者你也可以理解成枚举 LCP),如果遇到 mid 某位是 0,那么与 ai 这位相反的那棵子树内的 aj 就都是合法的,借此求出有多少个满足条件的 aj 即可。这部分是两个 log 的。

对于第二部分,考虑在 Trie 树上每个点 u 额外维护一个 fu,x 表示经过 uai 中有几个二进制下第 x 位是 1,这样把 ai 扔到 Trie 树上走的过程中,如果遇到 kth 某位是 0,那么与 ai 这位相反的那个子树内的数与 ai 异或后就一定是大于 kth 的,相当于是求 ai 异或其它经过这棵子树的 aj 的和,贡献可以直接拆位算出来。

这样的话时空都是两个 log 的,可以通过,但是空间其实可以去掉一个 log

考虑将 ai 从小到大排序,有这么个性质:经过 Trie 上某个结点的 ai 一定是一段连续的区间。

因此可以直接对原序列拆位做一个前缀和,需要统计某棵子树的贡献拆位时可以对应其在序列中的左右端点,差分计算贡献。

这样就是时间两个 log,空间单 log 了。

ABC252Ex K-th beautiful Necklace

题目链接

很好的神仙题!技巧性很强的题,感觉就是各种 trick 的杂糅。

下文中令 m 为原题中的 c。发现这个 n 的范围是很神奇的,让人很容易去想暴力的可行性。当 m 确定时直接爆搜的上界显然是 O((nm)m) 的,那么整体的上界具体到底是多少呢?事实上 m=3 时会取到 1011 左右。

直接搜显然是爆炸了,但是注意到如果折半的话那搜一半的复杂度就会开个根号,只有 105 的级别(上界其实是 5×105 左右),这是完全可以接受的,因此可以贪心地将颜色划分为大小乘积比较接近的两组,折半地将两组的选法先搜出来。

假设第一组搜出来的所有选法权值扔到序列 a 中,第二组搜出来的所有选法权值扔到序列 b 中,那么现在问题就转化成了:给你两个序列 ab,求 aibj 的第 k 大值。

一个直接的想法是二分答案 mid,把每个 bi 扔到 Trie 中,然后对于每个 ai 计算有多少 bj 满足 aibjmid 即可。分析下复杂度,我们令 T 表示搜一半的复杂度上界,那么时间复杂度为 O(Tlog2V),算下来快到 109,好的看来是似了。

考虑干掉二分的 log:方法是直接让所有的 ai 同时在 Trie 上移动。具体地,从高到低枚举答案每一位,计算每个 ai 在当前位的相反方向上的子树内的 bj 个数并求和,如果大于 k 说明答案这一位为 1,集体向该位相反方向移动即可,否则说明答案这一位为 0,全部向当前位的同方向移动,并将 k 减去刚刚算的个数即可。

现在的时间复杂度就是 O(TlogV) 的了,真的赢了。

posted @   KingPowers  阅读(134)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示