题意
给定 \(n\) 个数 \(a_1 \cdots a_n\) 和 \(q\) 个查询,每次查询区间 \([l,r]\) 中,\(a\) 的不同数的个数
sol
本题不强制在线,因此可以考虑离线算法,如莫队。
莫队是一个非常神奇的算法,它的基本思想是:将区间进行某种排序后,通过移动 \(l,r\) 指针,每次移动时处理答案的增减来得到答案。
如何排序
如果按照左右端点排序的话,左端点的移动次数为 \(O(n)\),但右端点的移动次数依然会被卡到 \(O(n^2)\),而莫队的思想是通过分块的方式,使左右端点移动次数均摊,即 \(O(n\sqrt 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;
}
标签:luoguSP3267,cnt,int,++,端点,query,include,block
From: https://www.cnblogs.com/XiaoJuRuoUP/p/-/luoguSP3267