[luoguSP3267] D-query

题意

给定 n 个数 a1anq 个查询,每次查询区间 [l,r] 中,a 的不同数的个数

sol

本题不强制在线,因此可以考虑离线算法,如莫队。
莫队是一个非常神奇的算法,它的基本思想是:将区间进行某种排序后,通过移动 l,r 指针,每次移动时处理答案的增减来得到答案。

如何排序

如果按照左右端点排序的话,左端点的移动次数为 O(n),但右端点的移动次数依然会被卡到 O(n2),而莫队的思想是通过分块的方式,使左右端点移动次数均摊,即 O(nn)
为此,需要将点按照 n 个点一块的大小分块,然后按照左端点所在块为第一关键字,右端点为第二关键字进行排序。
通常来说,还会使用玄学的奇偶性优化进行卡常,即当左端点块为奇数时,右端点升序,否则右端点降序,虽然很玄学,但是会快很多,具体为什么我也不知道(

如何移动指针

当使用莫队时,往往在扩大区间时增加答案,在缩减区间时减小答案。而在某些题目中,当答案减小到负数时会出现一些莫名其妙的问题,因此在移动时我们需要先处理扩大区间,再处理缩小区间

对于本题,增减答案时只需要记录一个桶即可

代码

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>

using namespace std;

const int N = 30005, M = 200005, K = 1000005;

struct Ask{
    int l, r;
    int id;
} g[M];

int block[N], ans[M];
int n, q;
int a[N];
int res = 0;
int cnt[K];

bool cmp(Ask a, Ask b){
    if (block[a.l] != block[b.l]) return block[a.l] < block[b.l];
    if (block[a.l] & 1) return a.r < b.r;
    return a.r > b.r;
}

void del(int x){
    cnt[a[x]] -- ;
    if (!cnt[a[x]]) res -- ;
}

void add(int x){
    if (!cnt[a[x]]) res ++ ;
    cnt[a[x]] ++ ;
}

int main(){
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);

    int sz = sqrt(n);
    int bcnt = ceil(1.0 * n / sz);

    for (int i = 1; i <= bcnt; i ++ )
        for (int j = (i - 1) * sz + 1; j <= i * sz; j ++ )
            block[j] = i;
    
    scanf("%d", &q);
    for (int i = 1; i <= q; i ++ ) scanf("%d%d", &g[i].l, &g[i].r), g[i].id = i;

    sort(g + 1, g + q + 1, cmp);

    int l = 1, r = 0;
    for (int i = 1; i <= q; i ++ ){
        int ql = g[i].l, qr = g[i].r;
        while (l > ql) add( -- l);
        while (r < qr) add( ++ r);
        while (l < ql) del(l ++ );
        while (r > qr) del(r -- );
        ans[g[i].id] = res;
    }

    for (int i = 1; i <= q; i ++ ) printf("%d\n", ans[i]);

    return 0;
}
posted @   是一只小蒟蒻呀  阅读(11)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示