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

知识点:Trie,异或,堆

原题面:LojLuogu

简述

给定一长度为 \(n\) 的数列 \(a\),给定参数 \(k\)。求前 \(k\) 大的区间异或和之和。
\(1\le n\le 5\times 10^5\)\(1\le k\le \min\left\{ \frac{n(n-1)}{2}, 2\times 10^5 \right\}\)\(0\le a_i\le 2^{32} - 1\)
2S,1G。

分析

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

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

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


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

此外,还有一种 \(O(n\log^2 w)\) 级别的复杂度与 \(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 @ 2021-01-27 09:02  Luckyblock  阅读(74)  评论(0编辑  收藏  举报