【普通莫队】2023牛客多校5 A

简介

莫队算法是由莫涛提出的算法。

在莫涛提出莫队算法之前,莫队算法已经在 Codeforces 的高手圈里小范围流传,但是莫涛是第一个对莫队算法进行详细归纳总结的人。

莫涛提出莫队算法时,只分析了普通莫队算法,但是经过 OIer 和 ACMer 的集体智慧改造,莫队有了多种扩展版本。

莫队算法可以解决一类离线区间询问问题,适用性极为广泛。同时将其加以扩展,便能轻松处理树上路径询问以及支持修改操作。
—— OI-Wiki

普通莫队

例题:给定一个长为n的序列 a1,a2,...,an,有m次查询,每次查询求区间 [l,r]中满足: li<j<krai=ak>aj的三元组(i,j,k)的个数。

莫队用于处理这样的问题:离线多次查询序列区间,每次查询都需要O(n)的时间计算答案,但是由区间[l,r]的答案计算[l+1,r],[l1,r],[l,r+1],[l,r1]的答案时只需要O(1)的时间。

当已知[l,r]的答案,要计算[l,r]的答案时,只需将l转移到l, r转移到r,将区间两个端点看成平面坐标系上的一个点的横纵坐标,转移过程所需要的步数即为两点之间的曼哈顿距离。

由此,我们可以将所有点在平面坐标系中画出来,最佳的转移方案即为一个最小生成树,其中两点之间的距离为曼哈顿距离,转移所需的步数为最小生成树的大小。

考虑对查询区间进行如下的排序方式,可以获得较优的转移顺序:

将序列按照n大小分块,从左到右标上序号,按照查询区间左端点所属分块序号为第一关键字,右端点大小为第二关键字排序,最终在n=m的情况下计算所有答案的时间复杂度为O(nn)

给出一个不太严谨的证明:

首先分块之后,每个块大的大小为n,块的数量为n,暴力计算每个块的第一个查询区间,每次计算O(n),共有n次,时间复杂度为O(nn)。在同一个块中,右端点单调递增,转移右端点的复杂度为O(n),左端点每次转移都在同一块中,复杂度为O(n),转移的总数为n次,总复杂度为O(nn)

对于上面的例题,首先用树状数组求出每个位置i前面满足aj<aij的数量,记为ci。为了O(1)转移,还需记录两个数组totisumi,分别表示当前区间中值为i的位置的数量和总和。

分四种情况:

rr+1,答案增加totar+1cr+1sumar+1

ll1,答案增加sumal1total1cl1

rr1,答案减少(totar1)cr(sumarcr)

ll+1,答案减少(sumalcl)(total1)cl

代码
#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;
}

posted @   Mcggvc  阅读(18)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示