「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
和 \(\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++ 参考手册。
-
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;
}