莫队算法讲解及例题

莫队的思想

对于区间查询数字出现次数的问题,我们可以使用莫队。
比如,当询问区间是 1 1 1~ n n n时,可以通过 O ( N ) O(N) O(N)的复杂度解决。
那接下来再询问 2 2 2~ n n n或者 1 1 1~ n n n − - 1 1 1时,就可以通过 O ( 1 ) O(1) O(1)的复杂度解决这个询问。
所以,当有m个询问的时候,我们就可以根据第 i i i个询问的结果推出第 i i i + + + 1 1 1个询问的结果。这样就不用每一次询问都用 O ( N ) O(N) O(N)去跑了。
但是这可以被卡:

1 1
n n
1 1
n n
……

于是就产生了优化版:对左端点进行排序。
但是这依旧可以被卡:

1 n
2 1
3 n
4 1
……

所以我们就可以对左端点进行分块(后面发文章讲)
2025年3月7日签到:分块文章已发布—>链接
经过
精密的计算
,可以得出每个块大小设为 n \sqrt{n} n 时,可以使时间复杂度最优。
说多了也没用,来看道例题:

例题

1.小B的询问(模版题)

题目传送门
莫队采用的是离线操作。
所以我们先用一个数组 q q q记录询问的区间。
接着,我们对 q q q数组按一下规则进行排序。

bool cmp(node a,node b){
	return (a.l/s)<(b.l/s)||(a.l/s==b.l/s&&a.r<b.r);
}

先按照块排序,在同一块内则按右端点排序。
接着来看计算的地方
( x + 1 ) 2 = x 2 + 2 x + 1 (x+1)^2=x^2+2x+1 (x+1)2=x2+2x+1,这是一个众所周知的等式。
所以,当 c i c_i ci的值增加 1 1 1时,总数应该增加 2 ∗ c i + 1 2*c_i+1 2ci+1
同理,当 c i c_i ci的值减少 1 1 1时,总数应该减少 2 ∗ c i − 1 2*c_i-1 2ci1(注意和增加时不同)
我们用数组 b b b记录元素出现的次数,那么……
增加

void add(int x){
	ans+=2*(b[x]++)+1;
}

减少

void del(int x){
	ans-=2*(b[x]--)-1;
}

接着就是从当前询问转到下一个询问的事情:

//上一个区间比当前区间大,缩小区间范围
while(l1<f[i].l)del(a[l1++]);
while(r1>f[i].r)del(a[r1--]);
//上一个区间比当前区间小,扩大区间范围
while(l1>f[i].l)add(a[--l1]);
while(r1<f[i].r)add(a[++r1]);
answer[f[i].id]=ans;//存储答案,注意不是answer[i]

到这里,核心代码就完了

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,m,k;
int a[N];
struct node{
	int l,r,id;
}f[N];
int b[N];
int s;
bool cmp(node a,node b){
	return (a.l/s)<(b.l/s)||(a.l/s==b.l/s&&a.r<b.r);
}
int ans;
void add(int x){
	ans+=2*(b[x]++)+1;
}
void del(int x){
	ans-=2*(b[x]--)-1;
}
int answer[N];
signed main(){
	ios::sync_with_stdio(0);
	cin>>n>>m>>k;
	for(int i=1;i<=n;i++)cin>>a[i];
	s=sqrt(n);
	for(int i=1;i<=m;i++){
		cin>>f[i].l>>f[i].r;
		f[i].id=i;
	}
	sort(f+1,f+1+m,cmp);
	int l1=1,r1=0;
	for(int i=1;i<=m;i++){
		while(l1<f[i].l)del(a[l1++]);
		while(r1>f[i].r)del(a[r1--]);
		while(l1>f[i].l)add(a[--l1]);
		while(r1<f[i].r)add(a[++r1]);
		answer[f[i].id]=ans;
	}
	for(int i=1;i<=m;i++)cout<<answer[i]<<'\n';
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值