初识莫队算法--例题:HH的项链

莫队算法:

①适用问题:大量区间的个数统计问题,由于莫队是典型的离线算法,故不能解决区间存在修改的问题。

比较典型的问题有“给定一个大小为N的数组,数组中所有元素的大小<=N。你需要回答M个查询。每个查询的形式是L,R。你需要回答在范围[ L,R ]中至少重复3次的数字(或者至少重复一次的数,即这个区间中不同的数)的个数。 ”

②基本思想:其实莫队就是基于暴力大法而进行的优化,先来介绍暴力的方法:既然询问的是各个区间中的数的个数问题,那么可以开一个数组cnt,用来记录各个数字出现的次数,每次询问,就用cnt数组对[L,R]中的各个数进行个数统计。显而易见,当询问次数很多的时候,明显会很耗时。而仔细分析后,不难发现,多个区间查询时,做了很多“重复工作”,这也就浪费了大量时间,所谓重复工作是指,当前询问的区间中的一部分,也出现在之前的询问中,那么每次都对这些重复的部分进行重新统计,就浪费了很多时间。

基于这个,莫队就是很好的利用了之前统计的区间,避免了很多重复统计。大概来说,就是设置两个指针L和R,对于每一个询问区间,分别移动L和R指针,直到L==当前询问区间的左边界 && R==当前询问区间的右边界。在移动L和R的过程中,同时更新统计信息(也就是cnt数组以及当前询问区间的答案)。

具体的讲解可以参见:https://blog.csdn.net/hnshhslsh/article/details/50582926

③模板代码:见这篇博客:https://www.cnblogs.com/five20/p/7603849.html

注意++L,L--等:何时前++和何时后++

④例题:

大视野oj1878:https://www.lydsy.com/JudgeOnline/problem.php?id=1878

AC代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define Fin             freopen("in.txt","r",stdin)
#define Fout            freopen("out.txt","w",stdout)
#define Case(T)         int T;for(scanf("%d",&T);T--;)
#define fo(i,a,b)              for(int i = a; i < b; ++i)
#define fd(i,a,b)              for(int i = a; i >= b; --i)
#define me(a,b) memset(a,b,sizeof(a))
#define fi(a,n,val)    fill(a,a+n,val)
#define Scand(n)       scanf("%d",&n)
#define Scand2(a,b)     scanf("%d%d",&a,&b)
#define Scand3(a,b,c)     scanf("%d%d%d",&a,&b,&c)
#define Scand4(a,b,c,d)     scanf("%d%d%d%d",&a,&b,&c,&d)
#define Scans(s)       scanf("%s",s)
using namespace std;
typedef long long ll;
ll gcd(ll a,ll b) { return b ? gcd(b,a%b): a; }
const int maxn = 1e6 + 50;
const int INF = 0xffffff;

#ifndef ONLINE_JUDGE

#endif // ONLINE_JUDGE


int n,m;            //n是整个区间的数字个数,m是查询区间的个数
int arr[maxn];      //arr是整个区间
int id[maxn];       //各个数字所属的块的编号
int ans[maxn];      //各个区间询问的答案
int cnt[maxn];      //当前区间中各个数出现的次数
int now;            //记录当前区间中不同数的个数

struct range{
    int l,r;        //区间的左右边界
    int askOrder;   //区间被询问的次序
//    friend bool operator < (const range &a, const range &b){
//        if(id[a.l] == id[b.l])      //同一个块中的,按右边界进行排序
//            return a.r < b.r;
//        return id[a.l] < id[b.l];   //非同一个块的,按所属的块进行排序
//    }
    bool operator <(const range & b)const{
        if(id[l] == id[b.l])
            return r < b.r;
        return l < b.l;
    }
};
range rangee[maxn];

//分块
void blocker(){
    int k = sqrt(n);
    fo(i, 1, n+1){
        id[i] = (i-1)/k+1;
    }
}

//x是指针,type表示指针是否在当前区间内,1表示在,0表示不在
void move(int x, int type){
    if(type == 1){
        cnt[arr[x]]++;
        if(cnt[arr[x]] == 1)    //arr[x]这个数的个数从0增加到1,说明该区间新增了一个数
            now++;
    }else if(type == 0){
        cnt[arr[x]]--;
        if(cnt[arr[x]] == 0)   //arr[x]这个数的个数从1减少为0,说明该区间减少了一个数
            now--;
    }
}

inline int read(){
    int sgn = 1; int cnt = 0;   //sgn表示正负号 cnt表示读取的数字
    char ch = getchar();
    while (ch < '0' || ch > '9') {      //读取非数字的字符,保留负号,忽略其余无关符号
        if(ch == '-')
            sgn = -sgn;
        ch = getchar();
    }
    while ('0' <= ch && ch <= '9') {
        cnt = cnt*10 + (ch-'0');
        ch = getchar();
    }
    return  sgn*cnt;
}

int main()
{
#ifndef ONLINE_JUDGE
    //Fin;
#endif // ONLINE_JUDGE
    n = read();
    fo(i, 1, n+1)
        arr[i] = read();
    blocker();

    m = read();
    fo(i, 1, m+1){
        rangee[i].l = read();   rangee[i].r = read();
        rangee[i].askOrder = i;
    }
    sort(rangee+1, rangee+m+1);
    
    int L = 0, R = 0;           //左右指针
    now = 0;
    for(int i = 1; i <= m; ++i){

        //指针不在当前区间内  注意是L++、R++
        while (L < rangee[i].l) {
            move(L++,0);
        }
        while (R > rangee[i].r) {
            move(R--,0);
        }
        //指针在当前区间内  注意是--L、--R
        while (L > rangee[i].l) {
            move(--L,1);
        }
        while (R < rangee[i].r) {
            move(++R,1);
        }
        ans[rangee[i].askOrder] = now;      //按询问的次序记录答案

    }
    //输出答案
    fo(i, 1, m+1){
        printf("%d\n",ans[i]);
    }
    return 0;
}

 

你可能感兴趣的:(acm)