可持久化 01-trie 简记

news/2025/3/13 18:13:43/文章来源:https://www.cnblogs.com/Ydoc770/p/18767186

本文略过了 trie 和 可持久化的介绍,如果没学过请先自学。

在求给定一个值 \(k\) 与区间中某些值的异或最大值时,可以考虑使用在线的数据结构可持久化 01-trie 来维护。

01-trie

01-trie 本身是用以求异或最大值的数据结构。

考虑板子题:给定 \(n\) 个数,\(m\) 次询问 \(k\) 与某个数异或的最大值。

我们肯定不能直接拿 \(k\) 与每个数异或,考虑把 \(k\) 拆成二进制,对于每一位单独做。
从高位到低位贪心。如果 \(k\) 的这一位是 \(1\) 那么我们就要尽可能的让选到的 \(a_i\) 这一位为 \(0\);反之要让 \(a_i\) 这一位为 \(1\)。这样我们取到的 \(a_i\) 一定是使 \(k\oplus a_i\) 最大的值。
这样我们就需要一种可以保留所有二进制串信息且占用空间尽可能小的数据结构,而 trie 恰好符合这一点。
01-trie 就是字符集为 \(\{0, 1\}\) 的 trie。
我们把每个数转成二进制从高位开始加入到 01-trie 中,每次根据 \(k\) 二进制位是 0/1 来贪心地更新答案即可。

复杂度 \(O(n\log v+m\log v)\)\(v\) 是值域。

可持久化思想

如果从一个数变成一个区间与 \(k\) 的异或最大值,我们就要使用可持久化思想来升级 01-trie。

考虑升级版板子题:给定 \(n\) 个数,\(m\) 次询问 \(k\)\(a_l\sim a_r\) 异或后缀异或的最大值,或者插入一个数。
我们也不能对于每个异或后缀开一棵 01-trie 来统计,这样时间空间双双爆炸。考虑差分,转化成前缀信息来做。
\(s_i=\oplus_{j=1}^{i}\),询问时找 \(k\oplus s_n\oplus s_{i-1}\) 最大值即是 \(k\)\(a_i\) 后缀异或最大值。
用可持久化思想,每次插入一个数就在前一个版本基础上更新,把新的 \(s_n\) 插入到 01-trie 里面。
根据上面的转化,查询时就可以直接查 \(k\oplus s_n\)\(rt_l\sim rt_r\) 这些版本的 01-trie 的异或最大值。
与没有持久化的 01-trie 类似,考虑对于两个版本的 01-trie 该怎么判断这一位能不能取 \(1\)
对 01-trie 上每条边 0/1 额外维护一个 sz[] ,表示这条边 0/1 在历史版本中一共出现的次数,加入新节点时就在上一个版本对应边基础上 +1。查询时就可以作差求出需要的 0/1 边是否存在。

这样我们就用同样的单 \(\log\) 时间复杂度解决了这个问题,唯一区别是可持久化占用的空间从线性 \(O(n)\) 变成了单 \(log\) \(O(n\log n)\)

代码实现

原题跳转:Luogu P4735 最大异或和

注意数组范围要开大 \(64\) 倍防止越界。

#include<bits/stdc++.h>
using namespace std;const int maxn = 6e5 + 10;
int n, m, s[maxn << 1];
int tot, ch[maxn << 5][2], sz[maxn << 5], rt[maxn << 1];void add(int u, int v, int t, int x) {if(t < 0) return;int i = (x >> t) & 1;ch[u][i] = ++tot, ch[u][i ^ 1] = ch[v][i ^ 1];sz[ch[u][i]] = sz[ch[v][i]] + 1;add(ch[u][i], ch[v][i], t - 1, x);
}
int ask(int u, int v, int t, int x) {if(t < 0) return 0;int i = (x >> t) & 1;if(sz[ch[u][i ^ 1]] - sz[ch[v][i ^ 1]]) {return (1 << t) + ask(ch[u][i ^ 1], ch[v][i ^ 1], t - 1, x);}else return ask(ch[u][i], ch[v][i], t - 1, x);
}int main() {ios :: sync_with_stdio(false); cin.tie(0); cout.tie(0);cin >> n >> m;  rt[0] = ++tot, add(rt[0], 0, 25, 0);for(int i = 1; i <= n; i++) {int x; cin >> x;s[i] = s[i - 1] ^ x;rt[i] = ++tot, add(rt[i], rt[i - 1], 25, s[i]);}for(int i = 1; i <= m; i++) {char op; cin >> op;if(op == 'A') {int x; cin >> x; ++n;s[n] = s[n - 1] ^ x, rt[n] = ++tot; add(rt[n], rt[n - 1], 25, s[n]);}if(op == 'Q') {int l, r, x; cin >> l >> r >> x; l--, r--;if(l == 0) cout << ask(rt[r], 0, 25, x ^ s[n]) << endl;else cout << ask(rt[r], rt[l - 1], 25, x ^ s[n]) << endl;}}return 0;
} 

例题

原题跳转:Luogu P5795 异或运算

给定 \(x\)\(y\) 数组,询问给定两个范围 \(u,d\)\(l,r\)\(k\),求 \(x_u\sim x_d\) 分别异或 \(y_l\sim y_r\) 的第 \(k\) 大值。其中 \(x\) 长度不超过 \(10^3\)\(y\) 长度不超过 \(3\times 10^5\),询问不超过 \(5\times 10^2\)

注意这个题数据范围非常抽象。两维分开维护很困难,但是一维可以直接可持久化 01-trie。那么考虑对 \(x\) 暴力,对 \(y\) 用可持久化 01-trie 维护。每次询问,暴力统计 \(x\) 数组中的贡献。
具体地,把 \(x_i\) 的每一位单独拿出来,由于是第 \(k\) 大,统计这一位异或起来为 \(1\) 的个数,如果大于等于 \(k\),就都走二进制位不同的那一位并且统计这一位的贡献;小于 \(k\) 就走二进制位相同的那一项。所有贡献加起来即可。

代码实现

#include<bits/stdc++.h>
using namespace std;const int maxn = 1e3 + 10, maxm = 3e5 + 10;
int n, m, q, x[maxn], y[maxm];int tot, ch[maxm << 6][2], sz[maxm << 6], rt[maxm], posl[maxn], posr[maxn];
void add(int u, int v, int t, int k) {if(t < 0) return;int i = (k >> t) & 1;ch[u][i] = ++tot, ch[u][i ^ 1] = ch[v][i ^ 1];sz[ch[u][i]] = sz[ch[v][i]] + 1;return add(ch[u][i], ch[v][i], t - 1, k), void(0);
}
int ask(int xl, int xr, int yl, int yr, int k) {for(int j = xl; j <= xr; j++) posl[j] = rt[yl - 1], posr[j] = rt[yr];int res = 0;for(int t = 31; t >= 0; t--) {int cnt = 0;for(int j = xl; j <= xr; j++) {int i = (x[j] >> t) & 1;cnt += sz[ch[posr[j]][i ^ 1]] - sz[ch[posl[j]][i ^ 1]];}if(cnt >= k) {res |= (1 << t);for(int j = xl; j <= xr; j++) {int i = (x[j] >> t) & 1;posl[j] = ch[posl[j]][i ^ 1];posr[j] = ch[posr[j]][i ^ 1];}}else {k -= cnt;for(int j = xl; j <= xr; j++) {int i = (x[j] >> t) & 1;posl[j] = ch[posl[j]][i];posr[j] = ch[posr[j]][i];}}}return res;
}int main() {ios :: sync_with_stdio(false); cin.tie(0); cout.tie(0);//	rt[0] = ++tot; add(rt[0], 0, 33, 0);cin >> n >> m;for(int i = 1; i <= n; i++) cin >> x[i];for(int i = 1; i <= m; i++) cin >> y[i], rt[i] = ++tot, add(rt[i], rt[i - 1], 31, y[i]);cin >> q;for(int i = 1; i <= q; i++) {int u, d, l, r, k; cin >> u >> d >> l >> r >> k;cout << ask(u, d, l, r, k) << endl;}return 0;
}

lxl 上课讲到的题,如果学过可持久化 01-trie 就是宝宝题

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/898286.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Electron 进程间通信(IPC)方法详解

Electron 是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架,它是基于 Chromium 和 Node.js 构建的,而 Chromium 本身是采用多进程架构的,所以 Electron 也是多进程的。 Electron 是一个多进程框架,它的进程主要分为两类:主进程(Main Process) 和 渲染进程(R…

从零开始的web前端学习-JavaScript

JavaScript 是一种运行在客户端(浏览器)的编程语言,实现人机互动效果:网页特效(监听用户的某些行为并令网页进行反馈) 表单验证(针对表单数据的合法性进行判断) 数据交互(获取后台数据并渲染到前端)JavaScript 组成ECMAScript:基础语法核心 Web APIs:DOM(页面文档…

【Azure Service Bus】分享使用 Python Service Bus SDK 输出SDK内操作日志

问题描述 使用Python代码消费Service Bus中的消息,默认情况 Console 中的信息都是通过 print 打印输出。 有时候需要调查更多SDK中的日志,那么如何才能让SDK输出更多的日志呢?问题解答 方法就是引入 Logging SDK,然后再初始化 ServiceBusClient 对象时,设置logging_enabl…

nvm和nodejs安装

nvm和nodejs安装安装 nvm 全名 node.js version management,顾名思义是一个nodejs的版本管理工具。通过它可以安装和切换不同版本的nodejs。首先下载安装包,可以用GitHub上的,可以有点看,也可以用一些镜像,然后点击安装一直下一步即可。 然后打开命令行,可以用nvm -v指令…

dify文件上传到http节点

dify系统上传sys.files变量是Array[File]类型,由于 HTTP 请求节点不支持 Array[File] 上传,需要单独处理每个文件,以下是实现此功能的步骤: 添加迭代节点 迭代输入选sys.files 输出选http请求body http请求body类型选form-data 键值选迭代的item.File 本文使用dify版本为0.…

可行性分析(第五组)

目录 第1章 系统分析 1.1 可行性分析 1.1.1 技术可行性分析 1.1.2 经济可行性分析 1.1.3 社会可行性分析 1.1.4 法律可行性分析 1.2 系统流程分析 1.2.1 系统开发总流程 1.2.2 登录流程 1.2.3 系统操作流程 1.2.4 系统性能分析 第1章 可行性分析 1.1可行性分析 下面分别从技术可…

C# 子窗体中调用父窗体中的方法(或多窗体之间方法调用)

看似一个简单的功能需求,其实很多初学者处理不好的,很多朋友会这么写:C# Code://父窗体是是frmParent,子窗体是frmChildA //在父窗体中打开子窗体 frmChildA child = new frmChildA(); child.MdiParent = this; child.Show();//子窗体调父窗体方法: //错误的调用!!!!!!!! …

Qt HTTP模块——调用API对话DeepSeek

HTTP模块 Qt的网络模块(QtNetwork)支持HTTP/HTTPS协议,提供异步、非阻塞的API,实现客户端与服务器之间的 HTTP 请求与响应交互。核心类:QNetworkAccessManager:负责协调网络操作(如GET/POST请求),管理请求队列和返回的响应。 QNetworkRequest:封装HTTP请求的详细信息…

Linux下环境变量

Linux打印环境变量: echo $PATH

GitLearning

创建新仓库 创建新文件夹,打开,然后执行 git init创建新的 git 仓库 (也可以直接 git clone 远程仓库) git clone /path/to/repository git clone username@host:/path/to/repository工作流 本地仓库由 git 维护的三棵树。第一个是工作目录,它持有实际文件;第二个是暂存区(…

ai理解需求生成测试用例-deepseek

ai:把用例捡起来!把用例捡起来! 给大模型需求文档,让它完成设计用例,编写用例,包括功能用例、接口用例、自动化测试用例,自执行~最后发送至工作群中 直接使用deepseek即可 执行一下看看: 调用ds分析需求: 生成功能/接口用例: 生成自动化用例: 看一下自动生成的功…

Win下的Cursor连MCP都是坑?

最近研究了下MCP(模型上下文协议),MCP是由Anthropic(Claude母公司)在2024年11月25日提出并开源的,算是一个比较新的东西。 目前已经被Claude桌面端、Cline、Continue、Cursor、Windsurf等工具应用。在Windows上还是有蛮多坑的,Mac调用会相对简单很多。先来说下MCP能做什…