[十二省联考2019]异或粽子 - 可持久化Trrie、堆

Description

小粽是一个喜欢吃粽子的好孩子。今天她在家里自己做起了粽子。

小粽面前有 \(n\) 种互不相同的粽子馅儿,小粽将它们摆放为了一排,并从左至右编号为 \(1\)\(n\)。第 \(i\) 种馅儿具有一个非负整数的属性值 \(a_i\)。每种馅儿的数量都足够多,即小粽不会因为缺少原料而做不出想要的粽子。小粽准备用这些馅儿来做出 \(k\) 个粽子。

小粽的做法是:选两个整数数 \(l\), \(r\),满足 \(1 \leqslant l \leqslant r \leqslant n\),将编号在 \([l, r]\) 范围内的所有馅儿混合做成一个粽子,所得的粽子的美味度为这些粽子的属性值的异或和。

小粽想品尝不同口味的粽子,因此它不希望用同样的馅儿的集合做出一个以上的粽子。

小粽希望她做出的所有粽子的美味度之和最大。请你帮她求出这个值吧!

一句话题意:求出前 \(k\) 大区间异或和。

\(n \leqslant 5 \times 10^5, k\leqslant 2 \times 10^5\)

Solution

60pts

暴力模拟。

100pts

求解区间异或问题的常用 \(\text{trick}\)

先计算出前缀异或和 \(s_i\),即 \(s_i = \bigoplus_{j=1}^{i}a_j\),区间异或就转化为了 \(val[l,r]=s_r \oplus s_{l-1}\)

考虑维护一个堆,先将每个点作为右端点且左端点在 \([l,r]\) 间的最大异或和放进堆里,然后每次取出堆顶,设最大值取得的点为 \(k\),就将原来的区间 \([l,r]\) 拆分成 \([l,k-1]\)\([k+1,r]\) 放进堆里(即次大值),这样重复取 \(K\) 次,取得的区间异或和一定是前 \(K\) 大的。

而固定右端点查询左端点在某个区间内的最大异或和可以用可持久化 \(\text{Trie}\) 来实现,方法类似于主席树。

PS: 数组要开够!异或和大于号小于号放在一起要加括号!

Code

Talk is cheap. Show me the code.

#include <bits/stdc++.h>
using namespace std;

const int _ = 2e7 + 10;
typedef long long ll;
int N, K, tot, rt[_], tr[_][2], pos[_];
ll a[_], sum[_];

int insert(int x, int p) {
  int y = ++tot, tmp = y;
  for (int i = 32; i >= 0; --i) {
    int now = (sum[p] >> i) & 1;
    tr[y][now ^ 1] = tr[x][now ^ 1];
    x = tr[x][now];
    y = tr[y][now] = ++tot;
  }
  pos[y] = p + 1;
  return tmp;
}

int query(int l, int r, ll val) {
  for (int i = 32; i >= 0; --i) {
    int now = (val >> i) & 1;
    if (tr[r][now ^ 1] != tr[l][now ^ 1])
      l = tr[l][now ^ 1], r = tr[r][now ^ 1];
    else
      l = tr[l][now], r = tr[r][now];
  }
  return pos[r];
}

struct data {
  int cur, l, r, t;
  data(int _cur, int _l, int _r) {
    cur = _cur, l = _l, r = _r;
    t = query(rt[_l - 1], rt[_r], sum[_cur]);
    // cout << "!" << t << endl;
  }
  bool operator<(const data& x) const {
    return (sum[cur] ^ sum[t - 1]) < (sum[x.cur] ^ sum[x.t - 1]);
  }
};

priority_queue<data> Q;

int main() {
#ifndef ONLINE_JUDGE
  freopen("zongzi.in", "r", stdin);
  freopen("zongzi.out", "w", stdout);
#endif
  scanf("%d%d", &N, &K);
  for (int i = 1; i <= N; ++i) {
    scanf("%lld", &a[i]);
    sum[i] = sum[i - 1] ^ a[i];
    rt[i] = insert(rt[i - 1], i - 1);
    Q.push(data(i, 1, i));
  }
  ll ans = 0;
  while (K--) {
    data now = Q.top();
    Q.pop();
    ans += sum[now.cur] ^ sum[now.t - 1];
    if (now.l < now.t) Q.push(data(now.cur, now.l, now.t - 1));
    if (now.t < now.r) Q.push(data(now.cur, now.t + 1, now.r));
  }
  printf("%lld\n", ans);
  return 0;
}
posted @ 2020-06-16 07:43  newbielyx  阅读(133)  评论(0编辑  收藏  举报