「十二省联考 2019」异或粽子
知识点:Trie,异或,堆
简述
给定一长度为 的数列 ,给定参数 。求前 大的区间异或和之和。
,,。
2S,1G。
分析
先取前缀异或和 ,将区间异或和转化为两前缀相异或的形式,问题变为求前 大的 的和。可能有贡献的 对呈现三角的形态,考虑令 翻倍,再使最终答案除 2,以除去有序对 的限制。
再将问题直接放到 上考虑,答案即 中前 大的任意两数异或值的和。这样可以保证原问题中前 大元素都会被统计到两次,且根据异或的自反性,不合法的满足 数对贡献为 0,不会影响答案。
对于一个确定的 ,考虑如何找到一个最大的 ,使得 最大。显然可以在数列 构成的 Trie 上贪心来在 的时间复杂度内解决。类比线段树上二分,在 Trie 上维护 后可以在同等时间复杂度内求得第 大的 。
考虑原问题中的前 大这一限制,想到用 「NOI2010」超级钢琴 的套路解决。定义状态 表示对于确定的 ,第 大的 是 。初始时枚举所有元素,按照上述 Trie 上二分的做法构建初始状态 并放入以 为关键字的大根堆中。每次取出堆顶状态 ,统计贡献 ,并将新状态 放入堆中。显然这样能够保证枚举到所有有贡献的状态。
任意时刻时堆中最多有 个元素, 插入操作会发生 次,上述算法总时间复杂度为 。
如果不想做第一步有序对转无序对的转化,可以考虑建立可持久化 Trie 树,构建初始状态时枚举 ,在第 棵 Trie 上查询,可以保证查询到的 一定满足 。理论时间复杂度与上一致,但是空间占用翻倍,顺带影响了时间,最后时空双双被吊打:(不可持久化 vs 可持久化)。
此外,还有一种 级别的复杂度与 无关的做法,可以参考:这题。
注意 LL
,以及空间限制。
代码
Trie
复制复制//知识点:Trie,异或,堆 /* By:Luckyblock */ #include <algorithm> #include <cctype> #include <cstdio> #include <cstring> #include <queue> #define pr std::pair #define mp std::make_pair #define LL long long const int kN = 5e5 + 10; //============================================================= LL n, k, ans, sum[kN]; std::priority_queue <pr <LL, pr <int, int> > > q; //============================================================= inline LL read() { LL 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 { const int kMaxNode = kN << 5; int node_num, siz[kMaxNode], tr[kMaxNode][2]; LL val[kMaxNode]; void Insert(LL val_) { int now_ = 0; for (LL i = 33; i >= 0; -- i) { int ch = val_ >> i & 1ll; if (!tr[now_][ch]) tr[now_][ch] = ++ node_num; now_ = tr[now_][ch]; siz[now_] ++; } val[now_] = val_; } LL Query(int now_, int rk_, LL lth_, LL val_) { if (lth_ < 0) return val[now_]; int ch = val_ >> lth_ & 1, nowsiz = siz[tr[now_][ch ^ 1]]; if (rk_ <= nowsiz) return Query(tr[now_][ch ^ 1], rk_, lth_ - 1, val_); return Query(tr[now_][ch], rk_ - nowsiz, lth_ - 1, val_); } } void Init() { n = read(), k = read(); Trie::Insert(0); for (int i = 1; i <= n; ++ i) { sum[i] = sum[i - 1] ^ read(); Trie::Insert(sum[i]); } } //============================================================= int main() { Init(); for (int i = 0; i <= n; ++ i) { LL mx = sum[i] ^ Trie::Query(0, 1, 33, sum[i]); q.push(mp(mx, mp(i, 1))); } for (int i = 1; i <= 2 * k; ++ i) { pr <LL, pr <int, int> > t = q.top(); q.pop(); int pos = t.second.first, rk = t.second.second; LL mx = t.first, newmx = sum[pos] ^ Trie::Query(0, rk + 1, 33, sum[pos]); ans += mx; q.push(mp(newmx, mp(pos, rk + 1))); } printf("%lld\n", ans >> 1ll); return 0; }
可持久化 Trie
// /* By:Luckyblock */ #include <algorithm> #include <cctype> #include <cstdio> #include <cstring> #include <queue> #define pr std::pair #define mp std::make_pair #define LL long long const int kN = 5e5 + 10; //============================================================= LL n, k, ans, sum[kN]; std::priority_queue <pr <LL, pr <int, int> > > q; //============================================================= inline LL read() { LL 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 { const int kMaxRoot = kN; const int kMaxNode = kN << 6; int node_num, root[kMaxRoot], siz[kMaxNode], tr[kMaxNode][2]; LL val[kMaxNode]; void Insert(int &now_, int pre_, int lth_, LL val_) { now_ = ++ node_num; siz[now_] = siz[pre_] + 1; tr[now_][0] = tr[pre_][0], tr[now_][1] = tr[pre_][1]; if (lth_ < 0) { val[now_] = val_; return ; } int ch = val_ >> lth_ & 1; Insert(tr[now_][ch], tr[pre_][ch], lth_ - 1, val_); } LL Query(int now_, int rk_, LL lth_, LL val_) { if (lth_ < 0) return val[now_]; int ch = val_ >> lth_ & 1, nowsiz = siz[tr[now_][ch ^ 1]]; if (rk_ <= nowsiz) return Query(tr[now_][ch ^ 1], rk_, lth_ - 1, val_); return Query(tr[now_][ch], rk_ - nowsiz, lth_ - 1, val_); } } void Init() { n = read(), k = read(); Trie::Insert(Trie::root[0],0, 35, 0); for (int i = 1; i <= n; ++ i) { sum[i] = sum[i - 1] ^ read(); Trie::Insert(Trie::root[i], Trie::root[i - 1], 35, sum[i]); } } //============================================================= int main() { Init(); for (int i = 1; i <= n; ++ i) { LL mx = sum[i] ^ Trie::Query(Trie::root[i - 1], 1, 35, sum[i]); q.push(mp(mx, mp(i, 1))); } for (int i = 1; i <= k; ++ i) { pr <LL, pr <int, int> > t = q.top(); q.pop(); int pos = t.second.first, rk = t.second.second; ans += t.first; if (rk + 1 < pos) { LL newmx = sum[pos] ^ Trie::Query(Trie::root[pos - 1], rk + 1, 35, sum[pos]); q.push(mp(newmx, mp(pos, rk + 1))); } } printf("%lld\n", ans); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】