1. 程式人生 > 實用技巧 >「Ynoi2016」掉進兔子洞

「Ynoi2016」掉進兔子洞

知識點:莫隊,bitset

原題面: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;
}