版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
引言
集训队队长莫涛 and 提莫队长 are coming ......
莫队算法
相传是国家集训队队长莫涛在一次比赛中想出的算法,所以称作莫队算法(提莫队长??),类似于暴力维护,但却非常巧妙,而且据说是可以对区间进行各种操作,几乎万能哎,还有各种改进版本,如带改莫队,树上莫队 and so on
算法思想
基本思想
我们先想象出两个指针curL和curR,以及我们要查询的区间[ L , R ]
当遍历到这种情况时,我们只需要将curL向左移动,以 O( 1 ) 的时间复杂度得到新的区间[ L , curR ]
再将curR向左移动,得到目标区间 [ L , R ]
但如果多组询问,且每组询问的L和R跨度极大呢
算法精髓
对于一个长度为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 )
然后右端点同理
完
提莫队长正在待命。。。