bzoj3809. Gty的二逼妹子序列
知识点: 莫队,分块
题意简述
给定一长度为 \(n\) 的数列 \(a\),\(m\) 次询问。
每次询问给定参数 \(l,r,a,b\),求区间 \([l,r]\) 内权值 \(\in [a,b]\) 的数的种类数。
\(1\le a_i\le n\le 10^5\),\(1\le m\le 10^6\)。
分析题意
发现莫队比较便于维护种类数,套一个莫队消去区间的限制。
考虑值域的限制,权值线段树进行维护。
单次 修改/查询 复杂度均为 \(O(\log n)\)。
设块大小为 \(\dfrac{n}{\sqrt{m}}\),总复杂度为 \(O(n\sqrt{m}\log n + m\log n)\)。
发现修改和查询的次数并不平均,而线段树查询修改的复杂度是平均的。
考虑能否替换成另一种 修改复杂度较小,查询复杂度较大的数据结构。
想到直接使用数组进行O1修改On查询
只有单点修改,考虑对值域分块,维护块内不同的数的个数,可在莫队左右端点移动顺便维护。
查询时,在查询值域内的完整块直接统计答案,不完整块暴力查询。
设块大小为 \(\dfrac{n}{\sqrt{m}}\),单次修改复杂度 \(O(1)\),查询复杂度 \(\sqrt{n}\),总复杂度为 \(O(n\sqrt{m} +m\sqrt{n})\)。
爆零小技巧
代码实现
//知识点:莫队,分块
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstring>
#define ll long long
const int kMaxn = 1e5 + 10;
const int kMaxm = 1e6 + 10;
const int kMaxSqrtn = 320 + 10;
//=============================================================
struct Que {
int l, r, a, b, id;
} q[kMaxm];
int n, m, a[kMaxn];
int block_size, block_num, L[kMaxn], R[kMaxn], bel[kMaxn];
int nowl = 1, nowr, cnt[kMaxn], sum[kMaxSqrtn];
int ans[kMaxm];
//=============================================================
inline int read() {
int f = 1, w = 0;
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 GetMax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void GetMin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
bool CompareQuery(Que fir, Que 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) a[i] = read();
for (int i = 1; i <= m; ++ i) {
q[i] = (Que) {read(), read(), read(), read(), i};
}
block_size = (int) sqrt(n), 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) {
L[++ block_num] = R[block_num - 1] + 1;
R[block_num] = n;
}
for (int j = 1; j <= block_num; ++ j) {
for (int i = L[j]; i <= R[j]; ++ i) {
bel[i] = j;
}
}
std :: sort(q + 1, q + m + 1, CompareQuery);
}
int Query(int l_, int r_) {
int ret = 0;
if (bel[l_] == bel[r_]) {
for (int i = l_; i <= r_; ++ i) ret += (cnt[i] > 0);
return ret;
}
for (int i = bel[l_] + 1; i <= bel[r_] - 1; ++ i) {
ret += sum[i];
}
for (int i = l_; i <= R[bel[l_]]; ++ i) ret += (cnt[i] > 0);
for (int i = L[bel[r_]]; i <= r_; ++ i) ret += (cnt[i] > 0);
return ret;
}
void Delete(int now_) {
cnt[a[now_]] --;
if (! cnt[a[now_]]) sum[bel[a[now_]]] --;
}
void Add(int now_) {
if (! cnt[a[now_]]) sum[bel[a[now_]]] ++;
cnt[a[now_]] ++;
}
//=============================================================
int main() {
Prepare();
for (int i = 1; i <= m; ++ i) {
int l = q[i].l, r = q[i].r, a = q[i].a, b = q[i].b, id = q[i].id;
while (nowl < l) Delete(nowl), nowl ++;
while (nowl > l) nowl --, Add(nowl);
while (nowr > r) Delete(nowr), nowr --;
while (nowr < r) nowr ++, Add(nowr);
ans[q[i].id] = Query(a, b);
}
for (int i = 1; i <= m; ++ i) printf("%d\n", ans[i]);
return 0;
}
作者@Luckyblock,转载请声明出处。