普通莫队算法
以前一直认为莫队算法很难,但是现在看了下,发现如果只是普通的莫队算法还是比较容易理解的。
https://www.luogu.org/blog/codesonic/Mosalgorithm 这篇洛谷的博客讲的很详细了。
莫队算法一般用于询问区间中,比如对于一个数列1~n在10000左右,然后又有m个询问,m也是10000的级别,每个询问问你在1~n的某个区间的什么什么满足条件的个数之类的。
那么显而易见如果用暴力的话,那么极限情况下的复杂度就是O(mn),再加上如果里面有什么排序之类的话,就是更得O(mnlogn)的复杂度了,那么肯定会tle的。
所以这个时候就需要莫对算法了。
那么莫队算法是什么。
其实莫队的思想就是离线查询。相当于在查询前,先把所有的查询的答案给储存下来,到时候再直接输出所有答案。
那么如何查询所需区间的答案呢,直接for循环肯定不行。这个时候就引入了一种暴力的思想,比如首先定义一个 l=0 和 r=0,代表初始的查询的指针的位置,然后将 l 和 r 与所要查询的区间 [ql,qr]来对比一下,如果 l<ql 那么就右移,然后判断这种变化会使区间的变量少了多少种,注意这种减要减去当前自己所在位置的情况 所以一般是 del(l++),如果是 l>ql 那么就要左移,这时候情况就会增加,但是当前位置的情况以及算入进去了,就需要 add(--l),对于r的变化也是同理。
以洛谷的板子题目为例 https://www.luogu.org/problem/P2709
void add(int x){
sum[c[x]]++;
ans+=2*sum[c[x]]-1;
//一开始ans存的是sum[c[x]]^2,然后要求(sum[c[x]]+1)^2,所以相减一下,
//注意这里你sum[c[x]]的值先加了个1,所以相当于多加了2,要减掉2
}
void del(int x){
sum[c[x]]--;
ans-=2*sum[c[x]]+1;
}
以及在移动的时候的变化:
for(ll i=1;i<=m;i++){
ll ql=q[i].l,qr=q[i].r;
while(l<ql) del(l++);
while(l>ql) add(--l);
while(r<qr) add(++r);
while(r>qr) del(r--);
anss[q[i].id]=ans;
}
虽然这样的移动每次都是O(1) ,但是每次询问的移动次数最多依然是n次,时间复杂度依然是O(nm),那么怎么加速呢?由于每次时间的耗时在与移动次数上,所以我们尽可能的只要减少移动次数就行,这时候就可以将询问先储存下来,再按照某种方式排序,让他减少移动的次数,就会变快一点。
关于排序的方法以及复杂度的证明,接下来就直接上图了,其实大概知道排序的写法就行。
然后就是排序的两种写法了:
bool cmp(node x,node y){
return (x.r/block)==(y.r/block)?x.l<y.l:x.r<y.r;
}
奇偶性排序是这样的:
bool cmp(node x,node y){
return (x.l/block)^(y.l/block)?x.l<y.l:(((x.l/block)&1)?x.r<y.r:x.r>y.r);
}
这样能快是因为指针移到右边后不用再跳回左边,而跳回左边后处理下一个块又要跳回右边,这样就能减少一半操作,理论上能快一倍
注意:分块时块的大小不是固定的,要根据题目具体分析,分析的过程如上面分析m极大时的复杂度。
然后就是洛谷模板题的代码了:
#include<bits/stdc++.h>
using namespace std;
#define ll long long int
const int maxn=50005;
ll c[maxn],sum[maxn];
struct node{
ll l,r,id;
}q[maxn];
ll anss[maxn];
ll block,ans=0;
bool cmp(node x,node y){
return (x.l/block)^(y.l/block)?x.l<y.l:(((x.l/block)&1)?x.r<y.r:x.r>y.r);
}
void add(int x){ sum[c[x]]++; ans+=2*sum[c[x]]-1; }//一开始ans存的是sum[c[x]]^2,然后要求(sum[c[x]]+1)^2,所以相减一下, //注意这里你sum[c[x]]的值先加了个1,所以相当于多加了2,要减掉2
void del(int x){ sum[c[x]]--; ans-=2*sum[c[x]]+1; }
int main(){
ll n,m,k;
cin>>n>>m>>k;
block=sqrt(n);
for(ll i=1;i<=n;i++){
cin>>c[i];
}
for(ll i=1;i<=m;i++){
cin>>q[i].l>>q[i].r;
q[i].id=i;
}
sort(q+1,q+m+1,cmp);
int l=1,r=0;
for(ll i=1;i<=m;i++){
ll ql=q[i].l,qr=q[i].r;
while(l<ql) del(l++);
while(l>ql) add(--l);
while(r<qr) add(++r);
while(r>qr) del(r--);
anss[q[i].id]=ans;
}
for(ll i=1;i<=m;i++){
printf("%lld\n",anss[i]);
}
return 0;
}