莫队

\(\text{luogu-2709}\)

小 B 有一个长为 \(n\) 的整数序列 \(a\),值域为 \([1,k]\)

他一共有 \(m\) 个询问,每个询问给定一个区间 \([l,r]\),求:

\[\sum\limits_{i=1}^k c_i^2 \]

其中 \(c_i\) 表示数字 \(i\)\([l,r]\) 中的出现次数。小 B 请你帮助他回答询问。

\(1\le n,m,k \le 5\times 10^4\)


莫队模板题。

把询问离线下来。

考虑用双指针 \(l,r\) 维护 \([l,r]\) 区间内的 \(c_i\) 信息,以及当前区间的答案 \(res\)

对于每个询问可以移动双指针至询问区间 \([l_i, r_i]\),此时的 \(res\) 就是询问答案,一次移动可以做到 \(O(1)\)

那么以什么顺序处理询问就是影响时间复杂度的关键,不优的排序方式会使时间复杂度变为 \(O(n^2)\)

考虑分块,把区间分 \(\sqrt n\) 个块,在同一个块内的按右端点升序排序,不在同一个块内的按左端点升序排序,这样的时间复杂度是 \(O(n \sqrt m)\) 的,本题 \(n,m\) 同阶。

时间复杂度分析:分块之后,左端点按块的序号排序,右端点升序。对于每个块来说,右端点总移动量为 \(n\),而每切换一次询问左端点最多移动 \(B\) 次。因此复杂度为 \(O(Bm+\frac{n^2}{B})\),当 \(B=\frac{n}{\sqrt m}\) 最优,复杂度是 \(O(n \sqrt m)\)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
#define ll long long
#define MAXN 50005

ll read() {
	ll x = 0, f = 1;
	char c = getchar();
	while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
	while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
	return x * f;
}

ll n, m, k, a[MAXN], c[MAXN], res, ans[MAXN], B;
struct node { ll l, r, id; } q[MAXN];

bool cmp(node x, node y) {
	if((x.l - 1) / B == (y.l - 1) / B) return x.r < y.r;
	return x.l / B < y.l / B;
}

ll f(ll x, ll f) { return x * x - (x + f) * (x + f); }

int main() {
	n = read(), m = read(), k = read(), B = sqrt(n);
	for(int i = 1; i <= n; i ++) a[i] = read();
	for(int i = 1; i <= m; i ++) q[i].l = read(), q[i].r = read(), q[i].id = i;
	sort(q + 1, q + m + 1, cmp);
	ll l = 1, r = 1; c[a[1]] ++, res = 1;
	for(int i = 1; i <= m; i ++) {
		while(r < q[i].r) c[a[++ r]] ++, res += f(c[a[r]], -1);
		while(l < q[i].l) c[a[l ++]] --, res += f(c[a[l - 1]], 1);
		while(r > q[i].r) c[a[r --]] --, res += f(c[a[r + 1]], 1);
		while(l > q[i].l) c[a[-- l]] ++, res += f(c[a[l]], -1);
		ans[q[i].id] = res;
	}
	for(int i = 1; i <= m; i ++) cout << ans[i] << "\n";
	return 0;
}
posted @ 2026-03-05 19:27  So_noSlack  阅读(20)  评论(0)    收藏  举报