【题解】P4688 [Ynoi2016] 掉进兔子洞
题意
给定一个长度为 \(n\) 的序列和 \(m\) 个询问,每个询问给出 \(3\) 个区间 \([l_1, r_1], [l_2, r_2], [l_3, r_3]\)。每次删去 一个 三个区间内共有的数,试求最终三个区间内的数的 个数和。询问之间互相独立。
注意每次仅删去 一个 共有的数而非将这种数全部删除。例如三个区间分别是 \([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 \leq n, m \leq 10^5, 1 \leq a_i \leq 10^9\)
思路
转化成:莫队 + bitset
。
很难用莫队同时维护三个区间,考虑 转化 成用莫队分别求出三个区间,最后合并答案。
设最终一共会删去 \(k\) 个数,显然答案为 \((r_1 - l_1 + 1) + (r_2 - l_2 + 1) + (r_3 - l_3 + 1) - 3k\),问题转化为求最终删去的数的个数。
发现上面的问题实际上是求某一特定值在多个区间内分别出现的次数的最小值,难以直接用莫队维护。观察数据范围,\(a_i \leq 10^9\),显然需要离散化。整道题的突破口在于对离散化特殊处理,从而转化成 bitset
优化莫队。
不妨将 \(a_i\) 离散化为整个序列中小于等于 \(a_i\) 的值的个数,那么便可以用 bitset
优化莫队来维护了。
做法:
将每个操作拆分成三个询问。
令 \(c_p\) 表示当前区间中值 \(p\) 的出现次数。不妨对于每一个操作维护一个 bitset
,同时对当前区间维护一个 bitset
,表示每个值是否出现过。端点移动时,若增加新数 \(x\),则令 \(x + c_x\) 为出现过,而后 \(c_x + 1\);反之,\(c_x - 1\),且令 \(x + c_x\) 为未出现过。
最后令当前区间的 bitset
与当前操作的 bitset
取交,最终 bitset
中 \(1\) 的个数即为最终删去的数的个数。
原理:
原本相同的值离散化得到的值不同,即相同的值有一段连续的下标表示。现在离散化时令 \(a_i\) 为序列中小于等于 \(a_i\) 的数的个数,实际是将使用到的下标初始化为起始下标,\(x + c_x\) 为现在使用到的下标。莫队时令 bitset
中下标为 \(x + c_x\) 处为 \(1\),相当于令 \(x\) 对应的值多出现 \(1\) 次。这样最终令 bitset
取交即为对每个值取最小的出现次数,取 \(1\) 的个数即为取删去的值的个数。反之同理。
由于空间限制,需要将询问分成多段处理。
代码
#include <cstdio>
#include <cstring>
#include <cmath>
#include <bitset>
#include <algorithm>
using namespace std;
const int maxn = 1e5 + 5;
const int maxm = 2.5e4 + 5;
int n, m;
int tot, cur;
int a[maxn], b[maxn];
int bel[maxn], cnt[maxn], ans[maxm];
bool flag[maxn];
bitset<maxn> res[maxm], vis;
struct node {
int l, r, id;
bool operator < (const node& rhs) const {
if (bel[l] ^ bel[rhs.l]) {
return bel[l] < bel[rhs.l];
}
return (bel[l] & 1 ? r < rhs.r : r > rhs.r);
}
} q[maxn];
namespace IO{//by cyffff
int len=0;
char ibuf[(1<<20)+1],*iS,*iT,out[(1<<26)+1];
#if ONLINE_JUDGE
#define gh() (iS==iT?iT=(iS=ibuf)+fread(ibuf,1,(1<<20)+1,stdin),(iS==iT?EOF:*iS++):*iS++)
#else
#define gh() getchar()
#endif
#define reg register
inline int read(){
reg char ch=gh();
reg int x=0;
reg char t=0;
while(ch<'0'||ch>'9') t|=ch=='-',ch=gh();
while(ch>='0'&&ch<='9') x=x*10+(ch^48),ch=gh();
return t?-x:x;
}
inline void putc(char ch){
out[len++]=ch;
}
template<class T>
inline void write(T x){
if(x<0)putc('-'),x=-x;
if(x>9)write(x/10);
out[len++]=x%10+48;
}
inline void flush(){
fwrite(out,1,len,stdout);
len=0;
}
}
using IO::read;
using IO::write;
using IO::flush;
using IO::putc;
inline void work(int tot) {
int l = 1, r = 0, len = 0;
vis.reset();
memset(ans, 0, (tot + 1) * sizeof(int));
memset(cnt, 0, (n + 1) * sizeof(int));
memset(flag, false, (n + 1) * sizeof(bool));
for (int i = 1; i <= tot; i++) {
for (int j = 1; j <= 3; j++) {
len++;
q[len].l = read(), q[len].r = read();
q[len].id = i;
ans[i] += (q[len].r - q[len].l + 1);
}
}
sort(q + 1, q + len + 1);
for (int i = 1; i <= len; i++) {
while (l > q[i].l) {
l--;
vis[a[l] + cnt[a[l]]] = true;
cnt[a[l]]++;
}
while (r < q[i].r) {
r++;
vis[a[r] + cnt[a[r]]] = true;
cnt[a[r]]++;
}
while (l < q[i].l) {
cnt[a[l]]--;
vis[a[l] + cnt[a[l]]] = false;
l++;
}
while (r > q[i].r) {
cnt[a[r]]--;
vis[a[r] + cnt[a[r]]] = false;
r--;
}
if (!flag[q[i].id]) {
res[q[i].id] = vis;
flag[q[i].id] = true;
} else {
res[q[i].id] &= vis;
}
}
for (int i = 1; i <= tot; i++) {
ans[i] -= res[i].count() * 3;
write(ans[i]);
putc('\n');
}
}
int main() {
n = read(), m = read();
int block = sqrt(n);
for (int i = 1; i <= n; i++) {
a[i] = read();
b[i] = a[i];
bel[i] = (i - 1) / block + 1;
}
sort(b + 1, b + n + 1);
for (int i = 1; i <= n; i++) {
a[i] = lower_bound(b + 1, b + n + 1, a[i]) - b;
}
while (m) {
if (m < (maxm - 5)) {
work(m);
break;
}
work(maxm - 5);
m -= (maxm - 5);
}
flush();
return 0;
}