「莫队」学习笔记

莫队

莫队算法是一种离线算法不强制在线,通常有多次区间询问
通过对询问进行排序,区间的伸长缩短来实现。

简而言之:莫队=离线+暴力+分块


问题引入

放道题:
luogu_P2709
题目简意:
有一个长为 n n n 的整数序列 a a a,值域为 [ 1 , k ] [1,k] [1,k]
共有 m m m 个询问,每个询问给定一个区间 [ l , r ] [l,r] [l,r],求: ∑ i = 1 k c i 2 \sum\limits_{i=1}^k c_i^2 i=1kci2
其中 c i c_i ci 表示数字 i i i [ l , r ] [l,r] [l,r] 中的出现次数。

乍看之下我好像只会暴力啊?


假设我们算完 [ L , R ] [L,R] [L,R] 的答案后现在要算 [ L ′ , R ′ ] [L',R'] [L,R] 的答案。由于可以在 O ( 1 ) O(1) O(1) 的时间下得到 [ L , R + 1 ] , [ L , R − 1 ] , [ L + 1 , R ] , [ L − 1 , R ] [L,R+1],[L,R-1],[L+1,R],[L-1,R] [L,R+1],[L,R1],[L+1,R],[L1,R] 的答案,所以计算 [ L ′ , R ′ ] [L',R'] [L,R] 的答案耗时 ∣ L − L ′ ∣ + ∣ R − R ′ ∣ |L-L'|+|R-R'| LL+RR,如果把 [ L , R ] [L,R] [L,R] [ L ′ , R ′ ] [L',R'] [L,R] 都看成平面上的点的话,那么时间开销就是两点的曼哈顿距离。

具体实现:

  • 先对序列进行分块操作,每块的长度为 n \sqrt n n
  • 然后以询问左端点所在的块为第一关键字,右端点大小为第二关键字进行排序,每个区间是由上一区间递推得到,所以单次询问时间复杂度是 O ( n ) O(n) O(n)
  • 但是在块中每次询问 L L L 的移动最大为 n \sqrt n n R R R 的移动总和最大为 n n n,块与块之间的移动最大为 n n n,所以总复杂度为 O ( ( n + m ) n ) O((n+m)\sqrt n) O((n+m)n )

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;

#define maxn 50005
#define int long long

struct node {
    int l, r, id, pos;
} a[maxn];

bool cmp(node x, node y) {
    if (x.pos == y.pos) return x.r < y.r;
    return x.pos < y.pos;
}

int b[maxn], n, m, K;
int cnt[maxn], Ans[maxn];

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n >> m >> K;
    int size = (int)sqrt(n);
    for (int i = 1; i <= n; i++) cin >> b[i];
    for (int i = 1; i <= m; i++) {
        cin >> a[i].l >> a[i].r;
        a[i].id = i;
        a[i].pos = (a[i].l - 1) / size + 1;
    }
    sort(a + 1, a + m + 1, cmp);
    int l = 1, r = 0;
    int ans = 0;
    for (int i = 1; i <= m; i++) {
        while (l > a[i].l) {
            l--;
            cnt[b[l]]++;
            ans += 2 * cnt[b[l]] - 1;
        }
        while (r < a[i].r) {
            r++;
            cnt[b[r]]++;
            ans += 2 * cnt[b[r]] - 1;
        }
        while (l < a[i].l) {
            cnt[b[l]]--;
            ans -= 2 * cnt[b[l]] + 1;
            l++;
        }
        while (r > a[i].r) {
            cnt[b[r]]--;
            ans -= 2 * cnt[b[r]] + 1;
            r--;
        }
        Ans[a[i].id] = ans;
    }
    for (int i = 1; i <= m; i++) cout << Ans[i] << '\n';
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值