「Ynoi2016」掉進兔子洞
知識點:莫隊,bitset
扯
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
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++ 參考手冊。
-
std::sort(first, last)
,以不降序排序範圍[first, last)
中的元素。不保證維持相等元素的順序。
注意是 左閉右開 區間,因此才有通常的std::sort(a+1, a+n+1)
的寫法。 -
bitset
的下標從1
開始,越界後會丟擲異常資訊。 -
關於 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;
}