莫队算法总结

       莫队算法主要是利用了曼哈顿距离的长度关系,将每一个询问的坐标抽象为一个点,然后将询问分块,进行排序(分块的原因是使得这些询问坐标的移动的曼哈顿距离达到 最小),排序之后再按照此时的顺序进行左右区间的移动,而在内部的实现的过程就要看各个题目的要求了,所以在这里主要是理解到,莫队算法的核心是:平面上移动曼哈顿距离最小 (利用分块求得平面上曼哈顿距离的最小生成树)+ 离线查询(无法在线查询),在解题的过程中也要想到使得每一次转移计算答案的时间也要是O(1)的,到此,莫队算法也就完成。

三个样例:

HH的项链

(询问区间内不同数的个数,temp记录答案,只要num[arr[x]]>0那么temp+=1)

HYSBZ oj 1878

HH有一串由各种漂亮的贝壳组成的项链。HH相信不同的贝壳会带来好运,所以每次散步 完后,他都会随意取出一
段贝壳,思考它们所表达的含义。HH不断地收集新的贝壳,因此他的项链变得越来越长。有一天,他突然提出了一
个问题:某一段贝壳中,包含了多少种不同的贝壳?这个问题很难回答。。。因为项链实在是太长了。于是,他只
好求助睿智的你,来解决这个问题。
Input
第一行:一个整数N,表示项链的长度。 
第二行:N个整数,表示依次表示项链中贝壳的编号(编号为0到1000000之间的整数)。 
第三行:一个整数M,表示HH询问的个数。 
接下来M行:每行两个整数,L和R(1 ≤ L ≤ R ≤ N),表示询问的区间。
N ≤ 50000,M ≤ 200000。
Output

M行,每行一个整数,依次表示询问对应的答案。

Sample Input
6
1 2 3 4 3 5
3
1 2 
3 5
2 6
Sample Output
2
2
4

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <math.h>
#define siz 50005

using namespace std;
struct Query{
    int L,R,id;
}node[siz*4];
int arr[siz],ans[siz*4],num[siz*20];
int n,m,unit;
bool cmp(Query a,Query b){
    if(a.L/unit!=b.L/unit) return a.L/unit<b.L/unit;
    return a.R<b.R;
}
void solve(){
    int temp=0;
    memset(num,0,sizeof(num));
    int L=1,R=0;
    for(int i=0;i<m;i++){
       /// cout<<node[i].L<<" "<<node[i].R<<endl;
        while(R<node[i].R){
            R++;
            if(num[arr[R]]!=0)
            temp-=1;
            num[arr[R]]++;
            if(num[arr[R]]!=0)
            temp+=1;
           // cout<<arr[R]<<"---111---"<<num[arr[R]]<<endl;
        }
        while(R>node[i].R){
            //R++;
            if(num[arr[R]]!=0)
            temp-=1;
            num[arr[R]]--;
            if(num[arr[R]]!=0)
            temp+=1;
          //  cout<<arr[R]<<"---222---"<<num[arr[R]]<<endl;
            --R;

        }
        while(L<node[i].L){
           /// R++;
           if(num[arr[L]]!=0)
            temp-=1;
            num[arr[L]]--;
            if(num[arr[L]]!=0)
            temp+=1;
            //cout<<arr[L]<<"---333---"<<num[arr[L]]<<endl;
            L++;
        }
        while(L>node[i].L){
            L--;
            if(num[arr[L]]!=0)
            temp-=1;
            num[arr[L]]++;
            if(num[arr[L]]!=0)
            temp+=1;
           // cout<<arr[L]<<"---444---"<<num[arr[L]]<<endl;
        }
        ans[node[i].id]=temp;
    }
    //cout<<temp<<endl;
    for(int i=0;i<m;i++){
        printf("%d\n",ans[i]);
    }
}
int main()
{
    while(~scanf("%d",&n)){
        for(int i=1;i<=n;i++){
            scanf("%d",&arr[i]);
        }
        unit=(int)sqrt(n);
        scanf("%d",&m);
        for(int i=0;i<m;i++){
            node[i].id=i;
            scanf("%d %d",&node[i].L,&node[i].R);
        }
        sort(node,node+m,cmp);
        solve();
    }
    return 0;
}

小Z的袜子(hose)

(邝斌巨巨模板,不解释了,用这道题学的 莫队算法,另外两道是自己写的)

作为一个生活散漫的人,小Z每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿。终于有一天,小Z再也无法忍受这恼人的找袜子过程,于是他决定听天由命……
具体来说,小Z把这N只袜子从1到N编号,然后从编号L到R(L 尽管小Z并不在意两只袜子是不是完整的一双,甚至不在意两只袜子是否一左一右,他却很在意袜子的颜色,毕竟穿两只不同色的袜子会很尴尬。
你的任务便是告诉小Z,他有多大的概率抽到两只颜色相同的袜子。当然,小Z希望这个概率尽量高,所以他可能会询问多个(L,R)以方便自己选择。

Input

输入文件第一行包含两个正整数N和M。N为袜子的数量,M为小Z所提的询问的数量。接下来一行包含N个正整数Ci,其中Ci表示第i只袜子的颜色,相同的颜色用相同的数字表示。再接下来M行,每行两个正整数L,R表示一个询问。

Output

包含M行,对于每个询问在一行中输出分数A/B表示从该询问的区间[L,R]中随机抽出两只袜子颜色相同的概率。若该概率为0则输出0/1,否则输出的A/B必须为最简分数。(详见样例)

Sample Input
6 4
1 2 3 3 3 2
2 6
1 3
3 5
1 6
Sample Output
2/5
0/1
1/1
4/15
【样例解释】询问1:共C(5,2)=10种可能,其中抽出两个2有1种可能,抽出两个3有3种可能,概率为(1+3)/10=4/10=2/5。
询问2:共C(3,2)=3种可能,无法抽到颜色相同的袜子,概率为0/3=0/1。
询问3:共C(3,2)=3种可能,均为抽出两个3,概率为3/3=1/1。注:上述C(a, b)表示组合数,组合数C(a, b)等价于在a个不同的物品中选取b个的选取方案数。
【数据规模和约定】30%的数据中 N,M ≤ 5000;60%的数据中 N,M ≤ 25000;100%的数据中 N,M ≤ 50000,1 ≤ L < R ≤ N,Ci ≤ N。

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <math.h>
#define siz 50005
#define LL long long
using namespace std;
struct Query{
    int L,R,id;
}node[siz];
LL gcd(LL a,LL b){
    if(b==0) return a;
    else return gcd(b,a%b);
}
struct Ans{
    LL a,b;
    void reduce(){
        LL d=gcd(a,b);
        a/=d,b/=d;
    }
}ans[siz];

int a[siz],num[siz];
int n,m,unit;
bool cmp(Query a,Query b){
    if(a.L/unit!=b.L/unit) return a.L/unit<b.L/unit;
    else return a.R<b.R;
}
void work(){
    LL temp=0;
    memset(num,0,sizeof(num));
    int L=1,R=0;
    for(int i=0;i<m;i++){
        while(R<node[i].R){

            R++;
            //cout<<L<<"--111---"<<R<<endl;
            temp-=(LL) num[a[R]]*num[a[R]];
            num[a[R]]++;
            temp+=(LL) num[a[R]]*num[a[R]];
        }
        while(R>node[i].R){
            //R++;
            //cout<<L<<"--222---"<<R<<endl;
            temp-=(LL) num[a[R]]*num[a[R]];
            num[a[R]]--;
            temp+=(LL) num[a[R]]*num[a[R]];
            --R;
        }
        while(L<node[i].L){
            //L++;
           // cout<<L<<"---3----"<<R<<endl;
            temp-=(LL) num[a[L]]*num[a[L]];
            num[a[L]]--;
            temp+=(LL) num[a[L]]*num[a[L]];
            L++;
        }
        while(L>node[i].L){
            L--;
            //cout<<L<<"---444--"<<R<<endl;
            temp-=(LL) num[a[L]]*num[a[L]];
            num[a[L]]++;
            temp+=(LL) num[a[L]]*num[a[L]];
        }
        ans[node[i].id].a=temp-(R-L+1);
        ans[node[i].id].b=(LL) (R-L+1)*(R-L);
        ans[node[i].id].reduce();
    }
}
int main()
{
    while(~scanf("%d %d",&n,&m)){
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
        }
        for(int i=0;i<m;i++){
            node[i].id=i;
            scanf("%d%d",&node[i].L,&node[i].R);
        }
        unit=(int)sqrt(n);
        sort(node,node+m,cmp);
        for(int i=0;i<m;i++){
           // cout<<node[i].L<<" "<<node[i].R<<endl;
        }
        work();
        for(int i=0;i<m;i++){
            printf("%lld/%lld\n",ans[i].a,ans[i].b);
        }
    }
    return 0;
}

Sequence

csu 1515

(求区间内abs(a[i]-a[j])==1的个数,思路:num[arr[i]]记录arr[i]的个数,要使得abs(arr[i]-x==1),那么x=arr[i]-1||x=a[rr]+1,所以,temp+=num[arr[i]+1]||temp+=num[arr[i]+1],

但是,问题来了,arr[]数组是32位整数,数组最大只能 开到arr[10^8],那么现在就只能使用map了,将arr[]数组按照从小到大的顺序map,意思是如果arr[i]>arr[j]那么map[arr[i]]>map[arr[j]],map的 值是从1到n而n最大为10^4,绰绰有余了,到此,此题再加上莫队算法就能解决了。)

Give you a sequence consisted of n numbers. You are required to answer how many pairs of numbers (ai, aj) satisfy that | ai - aj | = 1 and L ≤ i < j ≤ R.

Input

The input consists of one or more test cases.
For each case, the first line contains two numbers n and m, which represent the length of the number sequence and the number of queries. ( 1 ≤ n ≤ 10^4, 1 ≤ m ≤ 10^5 )
The second line consists of n numbers separated by n - 1 spaces.( 0 ≤ ai < 2^31 )
Then the m lines followed each contain two values Li and Ri.

Output

For each case just output the answers in m lines.

Sample Input
10 10
5 5 1 3 6 3 5 7 1 7
3 4
6 8
8 9
2 8
5 7
6 7
1 9
3 10
3 10
5 6
Sample Output
0
0
0
3
1
0
4
3
3
0
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <math.h>
#define siz 10005
#include <algorithm>
#include <map>
using namespace std;
struct Query{
    int L,R,id;
}node[siz*10];
int ans[siz*10];
int arr[siz],num[siz],unit,trr[siz],Np;
map<int,int>Mp;
int n,m;
void _Init(){
    Mp.clear();
    for(int i=1;i<=n;i++){
        trr[i]=arr[i];
    }
    sort(trr+1,trr+1+n);
    Np=unique(trr+1,trr+1+n)-trr-1;
    for(int i=1;i<=Np;i++){
        Mp[trr[i]]=i;
       // cout<<Mp[trr[i]]<<"&&&"<<trr[i]<<endl;
    }
}
bool cmp(Query a,Query b){
    if(a.L/unit!=b.L/unit) return a.L/unit<b.L/unit;
    return a.R<b.R;
}
void  solve(){
    int temp=0;
    memset(num,0,sizeof(num));
    int L=1,R=0;
    int ant=-1;
    for(int i=0;i<m;i++){
        while(R<node[i].R){
            R++;
            if(trr[Mp[arr[R]]]-trr[Mp[arr[R]]-1]==1){
                temp-=num[Mp[arr[R]]]*num[Mp[arr[R]]-1];
            }
            if(trr[Mp[arr[R]]+1]-trr[Mp[arr[R]]]==1){
                temp-=num[Mp[arr[R]]]*num[Mp[arr[R]]+1];
            }

            num[Mp[arr[R]]]++;
            if(trr[Mp[arr[R]]]-trr[Mp[arr[R]]-1]==1){
                temp+=num[Mp[arr[R]]]*num[Mp[arr[R]]-1];
            }
            if(trr[Mp[arr[R]]+1]-trr[Mp[arr[R]]]==1){
                temp+=num[Mp[arr[R]]]*num[Mp[arr[R]]+1];
            }
        }
        while(R>node[i].R){
            if(trr[Mp[arr[R]]]-trr[Mp[arr[R]]-1]==1){
                temp-=num[Mp[arr[R]]]*num[Mp[arr[R]]-1];
            }
            if(trr[Mp[arr[R]]+1]-trr[Mp[arr[R]]]==1){
                temp-=num[Mp[arr[R]]]*num[Mp[arr[R]]+1];
            }
            num[Mp[arr[R]]]--;
            if(trr[Mp[arr[R]]]-trr[Mp[arr[R]]-1]==1){
                temp+=num[Mp[arr[R]]]*num[Mp[arr[R]]-1];
            }
            if(trr[Mp[arr[R]]+1]-trr[Mp[arr[R]]]==1){
                temp+=num[Mp[arr[R]]]*num[Mp[arr[R]]+1];
            }
            R--;
        }
        while(L<node[i].L){
            if(trr[Mp[arr[L]]+1]-trr[Mp[arr[L]]]==1){
                temp-=num[Mp[arr[L]]+1]*num[Mp[arr[L]]];
            }
            if(trr[Mp[arr[L]]]-trr[Mp[arr[L]]-1]==1){
                temp-=num[Mp[arr[L]]]*num[Mp[arr[L]]-1];
            }
            num[Mp[arr[L]]]--;
            if(trr[Mp[arr[L]]+1]-trr[Mp[arr[L]]]==1){
                temp+=num[Mp[arr[L]]+1]*num[Mp[arr[L]]];
            }
            if(trr[Mp[arr[L]]]-trr[Mp[arr[L]]-1]==1){
                temp+=num[Mp[arr[L]]]*num[Mp[arr[L]]-1];
            }
            L++;
        }
        while(L>node[i].L){
            L--;
            if(trr[Mp[arr[L]]+1]-trr[Mp[arr[L]]]==1){
                temp-=num[Mp[arr[L]]+1]*num[Mp[arr[L]]];
            }
            if(trr[Mp[arr[L]]]-trr[Mp[arr[L]]-1]==1){
                temp-=num[Mp[arr[L]]]*num[Mp[arr[L]]-1];
            }
            num[Mp[arr[L]]]++;
            if(trr[Mp[arr[L]]+1]-trr[Mp[arr[L]]]==1){
                temp+=num[Mp[arr[L]]+1]*num[Mp[arr[L]]];
            }
            if(trr[Mp[arr[L]]]-trr[Mp[arr[L]]-1]==1){
                temp+=num[Mp[arr[L]]]*num[Mp[arr[L]]-1];
            }
        }
        ans[node[i].id]=temp;
    }
    for(int i=0;i<m;i++){
        printf("%d\n",ans[i]);
    }
}
int main()
{
    while(~scanf("%d%d",&n,&m)){
        for(int i=1;i<=n;i++){
            scanf("%d",&arr[i]);
        }
        _Init();
        unit=(int)sqrt(n);
        for(int i=0;i<m;i++){
            node[i].id=i;
            scanf("%d%d",&node[i].L,&node[i].R);
        }
        sort(node,node+m,cmp);
        solve();
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值