【普通莫队】2023牛客多校5 A
简介
莫队算法是由莫涛提出的算法。
在莫涛提出莫队算法之前,莫队算法已经在 Codeforces 的高手圈里小范围流传,但是莫涛是第一个对莫队算法进行详细归纳总结的人。
莫涛提出莫队算法时,只分析了普通莫队算法,但是经过 OIer 和 ACMer 的集体智慧改造,莫队有了多种扩展版本。
莫队算法可以解决一类离线区间询问问题,适用性极为广泛。同时将其加以扩展,便能轻松处理树上路径询问以及支持修改操作。
—— OI-Wiki
普通莫队
例题:给定一个长为n的序列 ,有m次查询,每次查询求区间 中满足: ,的三元组的个数。
莫队用于处理这样的问题:离线多次查询序列区间,每次查询都需要的时间计算答案,但是由区间的答案计算的答案时只需要的时间。
当已知的答案,要计算的答案时,只需将转移到, 转移到,将区间两个端点看成平面坐标系上的一个点的横纵坐标,转移过程所需要的步数即为两点之间的曼哈顿距离。
由此,我们可以将所有点在平面坐标系中画出来,最佳的转移方案即为一个最小生成树,其中两点之间的距离为曼哈顿距离,转移所需的步数为最小生成树的大小。
考虑对查询区间进行如下的排序方式,可以获得较优的转移顺序:
将序列按照大小分块,从左到右标上序号,按照查询区间左端点所属分块序号为第一关键字,右端点大小为第二关键字排序,最终在的情况下计算所有答案的时间复杂度为
给出一个不太严谨的证明:
首先分块之后,每个块大的大小为,块的数量为,暴力计算每个块的第一个查询区间,每次计算,共有次,时间复杂度为。在同一个块中,右端点单调递增,转移右端点的复杂度为,左端点每次转移都在同一块中,复杂度为,转移的总数为次,总复杂度为。
对于上面的例题,首先用树状数组求出每个位置前面满足的的数量,记为。为了转移,还需记录两个数组和,分别表示当前区间中值为的位置的数量和总和。
分四种情况:
到,答案增加
到,答案增加
到,答案减少
到,答案减少
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long lld;
const int N = 500005;
struct Que {
int l, r, num;
lld ans;
};
int n, m, a[N];
lld c[N], tot[N], sum[N];
vector <int> poss[N];
lld s[N];
int siz, id[N], numb;
Que que[N];
inline int lowbit(int x) {
return x & -x;
}
void add(int x, int k) {
for(; x < N; x += lowbit(x)) s[x] += k;
}
int Sum(int x) {
int summ = 0;
for(; x; x -= lowbit(x)) summ += s[x];
return summ;
}
bool cmp1(Que a, Que b) {
if(id[a.l] == id[b.l]) return a.r < b.r;
return id[a.l] < id[b.l];
}
bool cmp2(Que a, Que b) {
return a.num < b.num;
}
int main() {
// freopen("data.in", "r", stdin);
ios::sync_with_stdio(false);
cin >> n >> m;
siz = (int)sqrt(n); numb = ceil((double)n / (double)siz);
for(int i = 1; i <= numb; i++) {
for(int j = (i - 1) * siz + 1; j <= i * siz; j++) {
id[j] = i;
}
}
for(int i = 1; i <= n; i++) {
cin >> a[i];
poss[a[i]].push_back(i);
}
for(int i = 1; i <= m; i++) {
cin >> que[i].l >> que[i].r;
que[i].num = i;
}
for(int i = 1; i <= n; i++) {
if(poss[i].size()) {
for(auto j : poss[i]) {
c[j] = Sum(j);
}
for(auto j : poss[i]) {
add(j, 1);
}
}
}
sort(que + 1, que + 1 + m, cmp1);
int l = 1, r = 0; lld ans = 0;
for(int i = 1; i <= m; i++) {
int nowl = que[i].l, nowr = que[i].r;
while(l < nowl) {
ans = ans - ((sum[a[l]] - c[l]) - (tot[a[l]] - 1) * c[l]);
tot[a[l]]--; sum[a[l]] -= c[l]; l++;
}
while(l > nowl) {
ans = ans + (sum[a[l - 1]] - tot[a[l - 1]] * c[l - 1]);
tot[a[l - 1]]++; sum[a[l - 1]] += c[l - 1]; l--;
}
while(r < nowr) {
ans = ans + (tot[a[r + 1]] * c[r + 1] - sum[a[r + 1]]);
tot[a[r + 1]]++; sum[a[r + 1]] += c[r + 1]; r++;
}
while(r > nowr) {
ans = ans - ((tot[a[r]] - 1) * c[r] - (sum[a[r]] - c[r]));
tot[a[r]]--; sum[a[r]] -= c[r]; r--;
}
que[i].ans = ans;
}
sort(que + 1, que + 1 + m, cmp2);
for(int i = 1; i <= m; i++) {
cout << que[i].ans << endl;
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】