【SDOI2009】HH的项链——莫队算法第一题

莫队算法是一种离线的算法,基本上不支持修改操作,而且要可以改变询问区间的顺序才可以使用.

莫队算法其实是一种对序列先进行分块,然后按照查询区间的左端点所在的块作为第一关键字(注意是所在的块),查询的右端点所在的位置(这里是所在的位置了)作为第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这道题了.

猜你喜欢

转载自blog.csdn.net/hzk_cpp/article/details/80547572