学习了一下可持久化trie的有关姿势~其实还挺好理解的,代码也短小精悍。重点在于查询某个历史版本的trie树上的某条边是否存在,同样我们转化到维护前缀和来实现。同可持久化线段树一样,我们为了节省空间继承上一节点未修改的信息,修改的信息我们则新建一条链。节点上我们维护从最初的版本到当前版本这条路径一共出现了多少次,如果查询的最后版本记录这条路径出现的次数 > 查询的第一个版本的上一个版本的这条路径出现的次数,则说明这条路径存在在我们查询的范围内。
对于这道题来说,不大好处理的是查询是一段后缀,而后缀是实时修改的。我们考虑将后缀转化为前缀 :(x wedge (a[p] wedge a[p + 1] ... wedge a[n]) = x wedge (a[1] wedge a[2] ... wedge a[n] ) wedge (a[1] wedge a[2] wedge ... wedge a[p - 1]))。如果将后一个式子转化为前缀异或的形式的话,我们有原式等于 (x wedge s[n] wedge s[p - 1])。(x wedge s[n]) 是一个定值,所以我们只需要求出一段前缀与这个定值的异或和即可。所以我们可以利用可持久化trie树,查询对应范围内的值贪心获得答案。
#include <bits/stdc++.h> using namespace std; #define maxn 600100 int n, m, tot, root[maxn]; int cnt[maxn * 28], ch[maxn * 28][2]; int s[maxn]; int read() { int x = 0, k = 1; char c; c = getchar(); while(c < '0' || c > '9') { if(c == '-') k = -1; c = getchar(); } while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); return x * k; } void insert(int now, int pre, int t, int x) { if(t < 0) return; int i = (x >> t) & 1; ch[now][!i] = ch[pre][!i]; ch[now][i] = ++ tot; cnt[ch[now][i]] = cnt[ch[pre][i]] + 1; insert(ch[now][i], ch[pre][i], t - 1, x); } int Query(int now, int ath, int t, int x) { if(t < 0) return 0; int y = (x >> t) & 1; if(cnt[ch[ath][!y]] > cnt[ch[now][!y]]) return (1 << t) + Query(ch[now][!y], ch[ath][!y], t - 1, x); else return Query(ch[now][y], ch[ath][y], t - 1, x); } int main() { n = read(), m = read(); root[0] = ++ tot; insert(root[0], 0, 25, 0); for(int i = 1; i <= n; i ++) { int x = read(); s[i] = s[i - 1] ^ x; root[i] = ++ tot; insert(root[i], root[i - 1], 25, s[i]); } for(int i = 1; i <= m; i ++) { scanf("%s", s); if(s[0] == 'A') { int x = read(); ++ n; s[n] = s[n - 1] ^ x; root[n] = ++ tot; insert(root[n], root[n - 1], 25, s[n]); } else { int l = read(), r = read(), x = read(); l --, r --; if(l == 0) printf("%d ", Query(0, root[r], 25, x ^ s[n])); else printf("%d ", Query(root[l - 1], root[r], 25, x ^ s[n])); } } return 0; }