Luogu P4396 [AHOI2013] 作业 题解 [ 紫 ] [ 莫队 ] [ 值域分块 ]
作业:值域分块与莫队的经典应用。
值域分块其实和权值线段树差不多,就是一个普通的分块。但它却和莫队配合的很好,究其原因在于莫队有 \(\bm{O(n\sqrt n)}\) 次修改操作,但只有 \(\bm{O(n)}\) 次查询。如果使用树状数组、线段树等查询、修改都是 \(O(\log n)\) 的数据结构搭配莫队,那么总修改操作就达到了 \(O(n\sqrt n \log n)\) 量级。但是值域分块却可以做到 \(\bm{O(1)}\) 修改,\(\bm{O(\sqrt n)}\) 查询 / \(\bm{O(\sqrt n)}\) 修改,\(\bm{O(1)}\) 查询。因此可以把这个 \(O(\sqrt n)\) 的复杂度接到莫队中较小的那个复杂度上,起到平衡复杂度的作用。
对于此题,显然可以用莫队 + 树状数组维护答案,时间复杂度是 \(O(n\sqrt n \log n)\) 的。但是运用 \(O(1)\) 修改,\(O(\sqrt n)\) 查询的值域分块,却可以做到 \(O(n\sqrt n)\) 的复杂度,比树状数组更优秀。
注意一些值域分块的细节:值域分块的大小是 \(\bm V\) 而不是 \(\bm n\),块长的大小与莫队也一般不同。
补充一下 \(O(1)\) 查询,\(O(\sqrt n)\) 修改的值域分块的具体实现:我们维护每个位置的前缀和,修改的时候暴力修改散块内的前缀和,然后不断向后跳整块,对整块通过懒标记进行整体加。查询的时候查询前缀和之差即可。
#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi = pair<int, int>;
const int N = 100005, M = 1005, Q = 100005, V = 100000;
int n, m, a[N], B, Btot, tot[N], L = 1, R, ans[Q], ans2[Q];
struct Decompose{
int val[N], Sum[M], L[M], R[M], bl[N];
void build()
{
for(int i = 1; i <= Btot; i++)
{
L[i] = (i - 1) * B + 1;
R[i] = min(V, i * B);
for(int j = L[i]; j <= R[i]; j++)
{
bl[j] = i;
Sum[i] += val[j];
}
}
}
void update(int p, int v)
{
Sum[bl[p]] -= val[p];
val[p] = v;
Sum[bl[p]] += val[p];
}
int query(int l, int r)
{
int res = 0;
if(bl[l] == bl[r])
{
for(int i = l; i <= r; i++) res += val[i];
return res;
}
for(int i = l; i <= R[bl[l]]; i++) res += val[i];
for(int i = L[bl[r]]; i <= r; i++) res += val[i];
for(int i = bl[l] + 1; i < bl[r]; i++) res += Sum[i];
return res;
}
}dc, dc2;
struct Que{
int l, r, a, b, id;
bool operator < (const Que & t) const{
if(l / B != t.l / B) return (l < t.l);
if((l / B) & 1) return (r > t.r);
return (r < t.r);
}
}qs[Q];
void add(int p)
{
if(tot[a[p]] == 0) dc.update(a[p], 1);
tot[a[p]]++;
dc2.update(a[p], tot[a[p]]);
}
void del(int p)
{
tot[a[p]]--;
dc2.update(a[p], tot[a[p]]);
if(tot[a[p]] == 0) dc.update(a[p], 0);
}
int main()
{
//freopen("sample.in", "r", stdin);
//freopen("sample.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
B = sqrt(V);
Btot = (V + B - 1) / B;
dc.build();
dc2.build();
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i <= m; i++)
{
int l, r, a, b;
cin >> l >> r >> a >> b;
qs[i] = {l, r, a, b, i};
}
sort(qs + 1, qs + m + 1);
for(int i = 1; i <= m; i++)
{
int ql = qs[i].l, qr = qs[i].r, a = qs[i].a, b = qs[i].b, qid = qs[i].id;
while(R < qr) add(++R);
while(L > ql) add(--L);
while(R > qr) del(R--);
while(L < ql) del(L++);
ans[qid] = dc.query(a, b);
ans2[qid] = dc2.query(a, b);
}
for(int i = 1; i <= m; i++) cout << ans2[i] << " " << ans[i] << "\n";
return 0;
}