算法基础——莫队基础

知识点1: (普通)莫队的概念

(普通)莫队算法是一种优化的暴力算法,用于解决区间查询离线算法,基于分块的思想,时间复杂度 O ( m n + n n ) O(m\sqrt n+n\sqrt n) O(mn +nn )

什么时候使用莫队?

当对于一个查询 [ l , r ] [l,r] [l,r],能够在 O ( 1 ) O(1) O(1) 的时间内把 [ l , r ] [l,r] [l,r] 的答案转移到相邻的区间 [ l , r − 1 ] [l,r-1] [l,r1] [ l , r + 1 ] [l,r+1] [l,r+1] …此时可以考虑使用(普通)莫队算法

莫队的算法核心是分块和排序,即通过把询问离线化加以排序,把时间复杂度优化成 O ( m n + n n ) O(m \sqrt n+n\sqrt n) O(mn +nn )

怎么排序呢?
双关键字排序

对于左端点对复杂度的贡献,设 x i x_i xi 为第 i i i 块中左端点的个数,最坏情况下,每个块贡献的复杂度 O ( x i n ) O(x_i\sqrt n) O(xin ),(即左端点,在每个块的两端滚来滚去),合并后: O ( n ∑ i = 0 n x i ) = O ( m n ) O(\sqrt n\sum_{i=0}^{\sqrt n}x_i )=O(m\sqrt n) O(n i=0n xi)=O(mn )
对于右端点对复杂度的贡献,在每个分块中,右端点是非递减的,所以在每个块中的最坏复杂度是 O ( n ) O(n) O(n),总的复杂度即为 O ( n n ) O(n\sqrt n) O(nn )

所以总的时间复杂度即为: O ( m n + n n ) O(m \sqrt n+n\sqrt n) O(mn +nn )

例题一

Acwing 2492
经典的莫队算法运用

题目

HH 有一串由各种漂亮的贝壳组成的项链。

HH 相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一段贝壳,思考它们所表达的含义。

HH 不断地收集新的贝壳,因此他的项链变得越来越长。

有一天,他突然提出了一个问题:某一段贝壳中,包含了多少种不同的贝壳?

这个问题很难回答,因为项链实在是太长了。

于是,他只好求助睿智的你,来解决这个问题。

输入格式

第一行:一个整数 N N N,表示项链的长度。

第二行: N N N 个整数,表示依次表示项链中贝壳的编号(编号为 0 0 0 1000000 1000000 1000000 之间的整数)。

第三行:一个整数 M M M,表示 H H HH HH 询问的个数。

接下来 M M M 行:每行两个整数, L L L R R R,表示询问的区间。

输出格式

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

数据范围

1 ≤ N ≤ 50000 1\le N\le50000 1N50000,
1 ≤ M ≤ 2 × 1 0 5 1\le M\le 2×10^5 1M2×105,
1 ≤ L ≤ R ≤ N 1\le L\le R\le N 1LRN

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
#include<string>
#include<set>
#include<map>
#include<unordered_map>
#include<queue>

#define me(x,y) memset(x,y,sizeof x)
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define lowbit(x) -x&x
#define inf 0x3f3f3f3f
#define INF 0x7fffffff
#define cu(x) cout<<x<<"--"<<endl
#define ex exit(0)
#define pn puts("")

using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
typedef vector<int> vc;

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}

struct query
{
    int tag;
    int l,r;
};

const int N=5e4+10,M=2e5+10,C=1e6+10;
int n,m,siz,cnt;

int val[N];         //颜色总数
query ask[M];
int w[C];           //当前区间的颜色统计
int ans[M];

inline int get(int x)
{
    return (x-1)/siz;
}

bool operator<(const query&x1,const query& x2)
{
    if(get(x1.l)==get(x2.l))    return x1.r<x2.r;
    return get(x1.l)<get(x2.l);
}

inline void add(int x)
{
    int tag=val[x];
    if(!w[tag]) ++cnt;
    ++w[tag];
}

inline void del(int x)
{
    int tag=val[x];
    if(w[tag]==1)   --cnt;
    --w[tag];
}

int main()
{
    int i,j,l,r,k;
    
    scanf("%d",&n),siz=sqrt(n);
    for(i=1;i<=n;++i)
        scanf("%d",&val[i]);
    scanf("%d",&m);
    for(i=1;i<=m;++i)
    {
        scanf("%d%d",&l,&r);
        ask[i]={i,l,r};
    }
    
    sort(ask+1,ask+1+m);
    
    //i,j为当前的左右指针
    for(k=1,i=1,j=0;k<=m;++k)
    {
        l=ask[k].l,r=ask[k].r;
        
        while(j<r)  ++j,add(j);
        while(j>r)  del(j),--j;
        while(i<l)  del(i),++i;
        while(i>l)  --i,add(i);
        
        ans[ask[k].tag]=cnt;
    }
    
    for(i=1;i<=m;++i)
        printf("%d\n",ans[i]);

	return 0;
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值