「笔记」可持久化 Trie

写在前面

Q:什么时候需要可持久化?
A:想建一车 Trie 但是建不下的时候。

最好学习过可持久化线段树:Link

介绍

当使用 Trie 维护整数的二进制拆分时,可以发现每次插入一个数时,最多仅会访问并新建到 \(\log w\) 个节点。换言之,新的 Trie 与原 Trie 仅有 \(\log w\) 个节点不同。

考虑维护多个版本指针,每次插入时新建被访问到的 \(\log w\) 个节点,继承部分原树信息,并将新版本指针指向新的根。构建的时间复杂度为 \(O(n\log n)\)。空间复杂度为 \(O(n\log^2 n)\)

可持久化 Trie 可以将全局的二进制运算问题转化到区间上进行。

模板

P4735 最大异或和

给定一初始长度为 \(n\) 的非负整数序列 \(a\),给定 \(m\) 次操作:

  • 在序列末尾添加一给定的数 \(x\),序列长度 \(n+1\)
  • 给定参数 \(l,r,x\),需要找到一个位置 \(p\),满足 \(l \le p \le r\),使得: \(\left(\bigoplus\limits_{i=p}^{n} a_i\right)\oplus x\)最大,输出最大是多少。
    其中 \(\oplus\) 表示按位异或运算。

\(1\le n,m\le 3\times 10^5\)\(1\le a_i\le 10^7\)
1.5S,512MB。

看到区间异或,先转成前缀异或和形式,设 \(s_x = \bigoplus_{i=1}^{x} a_x\),则有:

\[\left(\bigoplus\limits_{i=p}^{n} a_i\right)\oplus x = s_{p-1}\oplus s_n\oplus x \]

\(s_n \oplus x\) 是定值,问题变为找到一个 \(p \in[l-1,r-1]\),使得 \(s_p\) 异或上 \(s_n \oplus x\) 最大。
一种暴力的想法是取出所有 \(s_i (i\in [l-1,r-1])\) 并用它们构成一棵 Trie,并在 Trie 上进行异或贪心。但时空均不允许我们暴力建 Trie,考虑可持久化。

具体地,进行所有插入操作时都新建历史版本,Trie 的每个节点都维护其子树中最晚插入的数的编号。查询时在第 \(r-1\) 个版本的 Trie 上贪心,这可以保证访问到的数的编号不大于 \(r-1\)
贪心时仅转移到子树中最晚插入的数的编号不小于 \(l-1\) 的节点,保证深入到叶节点得到的数的编号不小于 \(l-1\)

总复杂度 \(O((n +m)\log w)\) 级别,其中 \(w = \max a_i\)

代码

//知识点:
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <stack>
#define LL long long
const int kN = 6e6 + 10;
//=============================================================
int n, m, a[kN], sum[kN];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir, int sec) {
  if (sec > fir) fir = sec;
}
void Chkmin(int &fir, int sec) {
  if (sec < fir) fir = sec;
}
namespace Trie {
  #define ls (tr[now_][0])
  #define rs (tr[now_][1])
  const int kMaxRoot = kN;
  const int kMaxNode = kN << 2;
  int node_num, root[kMaxRoot], tr[kMaxNode][2], last[kMaxNode];
  void Insert(int &now_, int pre_, int val_, int lth_, int id_) {
    now_ = ++ node_num;
    ls = tr[pre_][0], rs = tr[pre_][1], last[now_] = id_;
    if (lth_ < 0) return ;
    int ch = val_ >> lth_ & 1;
    Insert(tr[now_][ch], tr[pre_][ch], val_, lth_ - 1, id_);
  }
  int Query(int now_, int val_, int lth_, int l_) {
    if (lth_ < 0) return sum[last[now_]];
    int ch = val_ >> lth_ & 1;
    if (last[tr[now_][ch ^ 1]] >= l_) {
      return Query(tr[now_][ch ^ 1], val_, lth_ - 1, l_);
    }
    return Query(tr[now_][ch], val_, lth_ - 1, l_);
  }
}
void Init() {
  n = read(), m = read();
  Trie::last[0] = -1;
  Trie::Insert(Trie::root[0], 0, 0, 25, 0);
  for (int i = 1; i <= n; ++ i) {
    sum[i] = sum[i - 1] ^ read();
    Trie::Insert(Trie::root[i], Trie::root[i - 1], sum[i], 25, i);
  }
}
//=============================================================
int main() { 
  Init();
  while (m --) {
    char opt[5]; scanf("%s", opt + 1);
    if (opt[1] == 'A') {
      ++ n;
      sum[n] = sum[n - 1] ^ read(); 
      Trie::Insert(Trie::root[n], Trie::root[n - 1], sum[n], 25, n);
    } else {
      int l = read(), r = read(), val = sum[n] ^ read();
      printf("%d\n", Trie::Query(Trie::root[r - 1], val, 25, l - 1) ^ val);
    }
  }
  return 0; 
}
posted @ 2021-02-01 17:07  Luckyblock  阅读(189)  评论(0编辑  收藏  举报