莫队算法是一种离线的算法,基本上不支持修改操作,而且要可以改变询问区间的顺序才可以使用.
莫队算法其实是一种对序列先进行分块,然后按照查询区间的左端点所在的块作为第一关键字(注意是所在的块),查询的右端点所在的位置(这里是所在的位置了)作为第2关键字排序.
然后维护两个指针l,r,表示现在维护的答案是区间[l,r]的询问答案.
之后就按照排完序的顺序不断模拟着移动指针l和r来获得答案.
时间复杂度为O(n*sqrt(n)). (设查询数量和序列长度都为n)
至于为什么是O(n*sqrt(n)),证明如下:
首先排序是O(n*log(n)).
之后我们可以处理每个块左端点在这个块内的所有询问.
那么我们可以发现,由于在左端点在同一个块的情况下,右端点是按照升序排列的,所以每次左端点在一个块内,右端点最多移动了n步.
而每次左端点到了不同的块时,右端点也顶多移动了n步,所以总共有sqrt(n)块,所以右端点的时间复杂度是O(n*sqrt(n)).
而左端点在同一个块中的变化,顶多移动了sqrt(n)步,所以最多这种移动只会移动n*sqrt(n)步.
又因为左端点是绝对不会从右边的块移动到左边的块的,所以左端点从一个块移动到另一个块的总移动的步数最多是2*n步.
所以左端点移动的总时间复杂度是O(n*sqrt(n)).
所以整个莫队算法的时间复杂度是O(n*sqrt(n)).
所以直接看例题.
题目:luogu1972.
题目大意:查询M次一个序列中某个区间的不同元素的数量,无修改.
这道题我们可以用离线算法做,也没有修改操作,可以使用莫队.
我们对查找区间直接用莫队算法专用排序先排一下序.
维护l和r指针,之后我们用一个数组cnt[i]表示区间[l,r]中数值i的出现次数.
接下来我们就可以把这道题套上莫队算法标准流程了.
代码如下:
#include<bits/stdc++.h> using namespace std; const int N=1000000; struct seg{ int l,r,in,id; //lin指l所在的块是第几个 }e[N>>2]; int n,m; int a[(N>>1)+1],cnt[N+1]; int ans[N>>2]; int L,R,cou=0; inline void into(){ scanf("%d",&n); for (int i=1;i<=n;i++) scanf("%d",&a[i]); scanf("%d",&m); for (int i=1;i<=m;i++) scanf("%d%d",&e[i].l,&e[i].r),e[i].id=i,e[i].in=e[i].l-1>>10; //我这里用1024个元素作为一个块 } bool cmp(seg a,seg b){ return a.in<b.in||a.in==b.in&&a.r<b.r; } inline void work(){ sort(e+1,e+1+m,cmp); L=1;R=1; cnt[a[1]]++;cou=1; for (int i=1;i<=m;i++){ while (R<e[i].r) {if (!cnt[a[R+1]]) cou++;cnt[a[++R]]++;} while (R>e[i].r) {cnt[a[R--]]--;if (!cnt[a[R+1]]) cou--;} while (L<e[i].l) {cnt[a[L++]]--;if (!cnt[a[L-1]]) cou--;} while (L>e[i].l) {if (!cnt[a[L-1]]) cou++;cnt[a[--L]]++;} ans[e[i].id]=cou; } } inline void outo(){ for (int i=1;i<=m;i++) printf("%d\n",ans[i]); } int main(){ into(); work(); outo(); return 0; }
可是这种写法在luogu上会TLE(TLE了真是不出所料),没办法一大波常数优化.
最后发现这东西打死不让我过去.
直接把块变成1个元素一个块.
还是过不了.
算了我假装我写过AC这道题了.