「Ynoi2016」掉进兔子洞

知识点:莫队,bitset

Link:Luogu darkbzoj

dark 能下数据还是很爽的。
用自带容器前务必详细阅读说明文档:C++ 参考手册

简述

给定一长度为 \(n\) 的序列 \(a\),给定 \(m\) 个询问,每次询问三个区间,把三个区间中同时出现的数一个一个删掉,问最后三个区间剩下的数的个数和,询问独立。
注意这里删掉指的是一个一个删,不是把等于这个值的数直接删完,比如三个区间是 \([1,2,2,3,3,3,3],[1,2,2,3,3,3,3]\)\([1,1,2,3,3]\),就一起扔掉了 \(1\)\(1\)\(1\)\(2\)\(2\)\(3\)
\(1\le n\le 10^5\)\(1\le a_i\le 10^9\)

分析

国际惯例先离散化,以下均在离散化的前提下展开。

显然对于一次询问,答案等于 三个区间的大小之和 \(- 3\times\) 并集的大小。区间大小之和容易求得,考虑如何求得并集的大小。

若只有一次询问,暴力的想法,分别统计三个区间中,每个数出现的次数。 并集中某数出现的次数,即为三个区间中该数出现次数的最小值。遍历一遍即可求得,离散化后复杂度 \(O(n)\),太菜了。

发现有区间限制,考虑使用莫队枚举区间。将一个询问拆成三个子询问,对于每个询问维护一个桶,储存 已枚举到的子询问 的并集。

进行取并集操作时,枚举值域,将维护的桶中的出现次数,与莫队枚举的区间的出现次数取 \(\min\)。 单次取并集操作复杂度 \(O(n)\),太菜了,并且对于每一个询问维护一个桶,空间显然无法承受。哪里可以找得到支持快速取并集,并且空间较小的数据结构呢?考虑把桶替换成 bitset,即可快速取并集,时空复杂度均可大幅缩小。

但换成 bitset 仍然有两个问题。

一是 bitset 仅能维护 01 信息,只能表示一个数是否出现过,而不能维护出现的次数。
这里有一种比较神的维护手段。

对于数列中两个 大小 相同的数,将它们看成两个不同的数。离散化时顺便维护 \(<\) 每一个数的数的个数 rank\(\le\) 每一个数的数的个数 cnt。令 bitset 中下标为 [rank[i]+1, rank[i]+cnt[i]] 的区间,维护 大小i 的数的出现情况。在莫队区间移动时,每新加入一个大小为 i 的数,将区间 [rank[i]+1, rank[i]+cnt[i]] 中第一个 0 位置置为 1。同理,删除时将最后一个 1 位置置为 0。

这样即可保证取并集时,并集中 [rank[i]+1, rank[i]+cnt[i]] 左侧连续 1 的个数,即为该数出现的次数。

二是直接开 \(m\)bitset 空间还是会挂掉。考虑把询问等分成三份,先后分别进行。时间换空间,只开 \(\frac{1}{3}\) 的空间即可。空间占用大概是 400MB + 的样子。

爆零小技巧

用自带容器前务必详细阅读说明文档:C++ 参考手册

  1. std::sort(first, last),以不降序排序范围 [first, last) 中的元素。不保证维持相等元素的顺序。
    注意是 左闭右开 区间,因此才有通常的 std::sort(a+1, a+n+1) 的写法。

  2. bitset 的下标从 1 开始,越界后会抛出异常信息。

  3. 关于 bitset 的成员函数.reset()
    .reset():将 bitset 全部置零。
    .reset(pos):仅将位置 pos 置零,相当于 .set(pos, false)

代码

//知识点:莫队,bitset 
/*
By:Luckyblock
*/
#include <algorithm>
#include <bitset>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstring>
#define ll long long
const int kMaxn = 1e5 + 10;
const int kMaxnn = 33334 + 10;
const int kMaxBlockSize = 400 + 10;
//===========================================================
struct Query {
  int l, r, id;
} q[3 * kMaxn];
int n, m, a[kMaxn], ans[kMaxnn];
int data_num, data[kMaxn], rank[kMaxn], cnt[kMaxn];
int L[kMaxBlockSize], R[kMaxBlockSize], bel[kMaxn];
std::bitset <kMaxn> bit[kMaxnn], now;
bool vis[kMaxnn];
int nowl = 1, nowr = 0;
//===========================================================
inline int read() {
  int w = 0, f = 1;
  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 Chkmin(int &fir, int sec) {
  if (sec < fir) fir = sec;
}
void Chkmax(int &fir, int sec) {
  if (sec > fir) fir = sec;
}
bool CompareQuery(Query fir, Query sec) {
  if (bel[fir.l] != bel[sec.l]) return bel[fir.l] < bel[sec.l];
  return fir.r < sec.r;
}
void Prepare() {
  n = read(), m = read();
  for (int i = 1; i <= n; ++ i) {
    data[i] = a[i] = read();
  }
  std::sort(data + 1, data + n + 1);
  data[++ data_num] = data[1];
  for (int i = 2; i <= n; ++ i) {
    if (data[i] != data[i - 1]) data_num ++;
    data[data_num] = data[i];
  }
  for (int i = 1; i <= n; ++ i) {
    a[i] = std::lower_bound(data + 1, data + data_num + 1, a[i]) - data;
  }
  for (int i = 1; i <= n; ++ i) cnt[a[i]] ++;
  for (int i = 1; i <= data_num; ++ i) {
    rank[i] = cnt[i - 1] + 1;
    cnt[i] += cnt[i - 1]; 
  }
}
void PrepareBlock() {
  int block_size = sqrt(n);
  int block_num = n / block_size;
  for (int i = 1; i <= block_num; ++ i) {
    L[i] = (i - 1) * block_size + 1;
    R[i] = i * block_size;
  }
  if (R[block_num] != n) {
    block_num ++;
    L[block_num] = R[block_num - 1] + 1;
    R[block_num] = n;
  }
  for (int i = 1; i <= block_num; ++ i) {
    for (int j = L[i]; j <= R[i]; ++ j) {
      bel[j] = i;
    }
  }
}
void koishi() {
  int satori = 5;
}
void Add(int pos_) {
  now[rank[a[pos_]]] = true;
  ++ rank[a[pos_]];
}
void Del(int pos_) {
  -- rank[a[pos_]];
  now[rank[a[pos_]]] = false;
}
void Solve(int m_) {
  int q_num = 0;
  now.reset();
  memset(vis, false, sizeof (vis));
  for (int i = 1; i <= data_num; ++ i) rank[i] = cnt[i - 1];

  for (int i = 1; i <= m_; ++ i) {
    ans[i] = 0;
    for (int j = 1; j <= 3; ++ j) {
      q[++ q_num] = (Query) {read(), read(), i};
      ans[i] += (q[q_num].r - q[q_num].l + 1);
    }
  }
  std::sort(q + 1, q + q_num + 1, CompareQuery);

  nowl = 1, nowr = 0;
  for (int i = 1; i <= q_num; ++ i) {
    int l = q[i].l, r = q[i].r, id = q[i].id;
    while (nowl > l) nowl --, Add(nowl);
    while (nowr < r) nowr ++, Add(nowr);
    while (nowl < l) Del(nowl), nowl ++;
    while (nowr > r) Del(nowr), nowr --;
    if (! vis[id]) {
      bit[id] = now;
      vis[id] = true;
    } else {
      bit[id] &= now;
    }
  }
  for (int i = 1; i <= m_; ++ i) {
    printf("%lld\n", 1ll * ans[i] - 3ll * bit[i].count());
  }
}
//===========================================================
int main() {
  Prepare();
  PrepareBlock();
  if (m <= kMaxnn - 10) {
    Solve(m);
    return 0;
  }
  int boundary[4] = {0, m / 3, m / 3 * 2, m}; 
  for (int i = 1; i <= 3; ++ i) {
    Solve(boundary[i] - boundary[i - 1]);
  }
  return 0;
}
posted @ 2020-10-09 20:27  Luckyblock  阅读(110)  评论(0编辑  收藏  举报