莫队
\(\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;
}
本文来自博客园,作者:So_noSlack,转载请注明原文链接:https://www.cnblogs.com/So-noSlack/p/19674758

浙公网安备 33010602011771号