C++学习笔记——解析莫队算法

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_44013342/article/details/96422176

引言

集训队队长莫涛 and 提莫队长 are coming ......


莫队算法

相传是国家集训队队长莫涛在一次比赛中想出的算法,所以称作莫队算法(提莫队长??),类似于暴力维护,但却非常巧妙,而且据说是可以对区间进行各种操作,几乎万能哎,还有各种改进版本,如带改莫队,树上莫队 and so on


算法思想

基本思想

我们先想象出两个指针curL和curR,以及我们要查询的区间[ L , R ]

当遍历到这种情况时,我们只需要将curL向左移动,以 O( 1 ) 的时间复杂度得到新的区间[ L , curR ]

再将curR向左移动,得到目标区间 [ L , R ]

但如果多组询问,且每组询问的L和R跨度极大呢

算法精髓

对于一个长度为n的区间,将他分为\sqrt{n}块,算出每个左端点所在的块,然后排序

如果所处块相等,则按右端点大小排序,否则按左端点大小排序

bool cmp( node x , node y ) {
	return x.l/cnt == y.l/cnt ? x.r < y.r : x.l < y.l;
}//cnt为块数

经过这种精巧的分块思想排序,就可以大大降低时间复杂度了


算法实现

先来一个例题

一个长度为n的区间,m个查询,问在区间[ L , R ]内有多少个数的出现次数为 k 

直接上代码(可以当做模板)

后面重点讲解(敲黑板,使劲敲的那种重点)

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define ll long long
using namespace std;

ll ans[50005] , ans_now ;//存储答案
int n , m , k , cnt ;//cnt表示块数
int curL = 1 , curR = 0 , a[50005] ;
int c[50005] ;//存储每个数出现次数
struct node {
	int l , r , id ;
}que[50005];

bool cmp( node x , node y ) {//分块排序
	return x.l/cnt == y.l/cnt ? x.r < y.r : x.l < y.l;
}

void add( int x ) {//区间扩展添加
	c[a[x]] ++ ;
	if( c[a[x]] == k )
		ans_now ++ ;
	else if( c[a[x]]-1 == k )
		ans_now -- ;
}

void dele( int x ) {//区间缩小删除
	c[a[x]] -- ;
	if( c[a[x]] == k )
		ans_now ++ ;
	else if( c[a[x]]+1 == k )
		ans_now -- ;
}

int main() {
	scanf("%d%d%d", &n , &m , &k );
	cnt = sqrt(n) ;
	for( int i = 1 ; i <= n ; ++ i )
		scanf("%d", &a[i] );
	for( int i = 1 ; i <= m ; ++ i ) {
		scanf("%d%d", &que[i].l , &que[i].r );
		que[i].id = i ;
	}
	sort( que+1 , que+m+1 , cmp );
	for( int i = 1 ; i <= m ; ++ i ) {//莫队一波
		int L = que[i].l , R = que[i].r ;
		while( curL < L )//重点重点重点
			dele(curL++) ;
		while( curL > L )
			add(--curL) ;
		while( curR > R )
			dele(curR--) ;
		while( curR < R )
			add(++curR) ;
		ans[que[i].id] = ans_now ; 
	}
	for( int i = 1 ; i <= m ; ++ i )
		printf("%lld\n", ans[i] );
} 

重点来了,重中之重

为什么在移动指针时,对区间修改一会先++,一会后++,花里胡哨的,有什么用???

大大的有用啊

如,dele( ++x ) 是先将x+1,再执行dele里的操作

而 dele(x++)是先执行dele里的操作,再将x+1

当 curL < L 时,curL向右移动到curL+1,要删除的是curL,所以是 dele( curL++ )

当 curL > L 时,curL向左移动到curL-1,要添加的是curL-1,所以是 add( --curL )

然后右端点同理


提莫队长正在待命。。。

 

猜你喜欢

转载自blog.csdn.net/qq_44013342/article/details/96422176