「十二省联考 2019」异或粽子

知识点:Trie,异或,堆

原题面:LojLuogu

简述

给定一长度为 n 的数列 a,给定参数 k。求前 k 大的区间异或和之和。
1n5×1051kmin{n(n1)2,2×105}0ai2321
2S,1G。

分析

先取前缀异或和 sum,将区间异或和转化为两前缀相异或的形式,问题变为求前 k 大的 sumsuml1(l1<r) 的和。可能有贡献的 l,r 对呈现三角的形态,考虑令 k 翻倍,再使最终答案除 2,以除去有序对 l1<r 的限制。
再将问题直接放到 sum 上考虑,答案即 sum0sumn 中前 2k 大的任意两数异或值的和。这样可以保证原问题中前 k 大元素都会被统计到两次,且根据异或的自反性,不合法的满足 l1=r 数对贡献为 0,不会影响答案。

对于一个确定的 sumx,考虑如何找到一个最大的 sumy,使得 sumxsumy 最大。显然可以在数列 sum 构成的 Trie 上贪心来在 O(logw) 的时间复杂度内解决。类比线段树上二分,在 Trie 上维护 size 后可以在同等时间复杂度内求得第 t 大的 sumxsumy
考虑原问题中的前 k 大这一限制,想到用 「NOI2010」超级钢琴 的套路解决。定义状态 (v,x,t) 表示对于确定的 sumx,第 t 大的 sumxsumyv。初始时枚举所有元素,按照上述 Trie 上二分的做法构建初始状态 (v,x,1) 并放入以 v 为关键字的大根堆中。每次取出堆顶状态 (v,x,t),统计贡献 v,并将新状态 (v,x,t+1) 放入堆中。显然这样能够保证枚举到所有有贡献的状态。

任意时刻时堆中最多有 n 个元素, 插入操作会发生 n+k 次,上述算法总时间复杂度为 O((n+k)(logn+logw))


如果不想做第一步有序对转无序对的转化,可以考虑建立可持久化 Trie 树,构建初始状态时枚举 sumy,在第 y1 棵 Trie 上查询,可以保证查询到的 sumx 一定满足 x<y。理论时间复杂度与上一致,但是空间占用翻倍,顺带影响了时间,最后时空双双被吊打:(不可持久化 vs 可持久化)。

此外,还有一种 O(nlog2w) 级别的复杂度与 k 无关的做法,可以参考:这题

注意 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;
}
posted @   Luckyblock  阅读(80)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 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】
点击右上角即可分享
微信分享提示