莫队算法 学习笔记

概述

莫队算法是一种离线算法,普通莫队可以 \(O(n\sqrt{n})\) 解决一些区间查询问题。这个问题需要满足区间 \([l,r]\) 的答案能快速求出区间 \([l-1,r],[l+1,r],[l,r+1],[l,r-1]\) 的答案。经过一些扩展可以完成修改、上树等操作。

普通莫队

例题

P7764

首先考虑一种暴力:维护两个指针 \(l,r\),暴力地在询问之间转移。比如这一题离散化后维护一个数组表示数字出现的个数,移动指针添数删数即可。

void add(int x){
  if(cnt[x]==2)now--;
  cnt[x]++;
  if(cnt[x]==2)now++;
}
void del(int x){
  if(cnt[x]==2)now--;
  cnt[x]--;
  if(cnt[x]==2)now++;
}
for(int i=1,l=1,r=0;i<=m;i++){
  while(l>q[i].l)add(a[--l]);
  while(r<q[i].r)add(a[++r]);
  while(l<q[i].l)del(a[l++]);
  while(r>q[i].r)del(a[r--]);
  ans[q[i].id]=now;
}

但是这样可能导致指针移动过多,因此还是 \(O(n^2)\) 的。实际上这种暴力与莫队算法只差一个将询问排序:

将序列分块,按左端点所在的块排序,左端点相同则按右端点排序。这样能保证 \(O(n\sqrt{n})\)

struct query{
  int l,r,id;
}q[500005];
bool cmp(query a,query b){
  return a.l/bn==b.l/bn?a.r<b.r:a.l<b.l;
}

感性地证明一下:设块长 \(B\)。对于左端点相同的询问,右端点移动是 \(O(n)\) 的。而块数为 \(\frac nB\),相乘就是 \(O(\frac{n^2}{B})\)。而左端点一次移动最多为块长,共 \(m\) 次,复杂度 \(O(mB)\)\(B=\frac{n}{\sqrt m}\) 最优,\(O(n\sqrt m)\)\(n,m\) 同阶就是块长 \(\sqrt n\),复杂度 \(O(n\sqrt n)\)

AT_tenka1_2014_final_d

\(T\) 个询问,每个询问给定 \(n,m\),求 \(f(n,m)=\sum_{i-0}^{m}C_n^i\bmod1000000007\)\(T,n,m\leq 10^5\)(多组询问组合数前缀和)。

这题看似与区间不相关,实际上也可以用莫队。

莫队的本质是维护多个指针,不一定与区间相关,甚至 A+B problem 多组询问也可以用莫队。

对于这题维护指针 \(n,m\),分类讨论如何转移。

对于 \(m\)

\[f(n,m+1)=f(n,m)+C_n^{m+1} \]

对于 \(n\)

\[\begin{aligned}f(n+1,m)&=\sum_{i=0}^{m}C_{n+1}^i \\&=\sum_{i=0}^{m}C_n^i+C_n^{i-1}\\&=2\sum_{i=0}^{m}C_{n}^i-C_n^m\\&=2f(n,m)-C_n^m\end{aligned} \]

预处理一下组合数即可 \(O(1)\) 转移。

#include<bits/stdc++.h>
using namespace std;
const int mod=1000000007; 
int t,bn,now=1,fac[100005],vac[100005],ans[100005];
struct query{
  int n,m,id;
}q[100005];
bool cmp(query a,query b){
  return a.m/bn==b.m/bn?(a.m/bn&1?a.n<b.n:a.n>b.n):a.m<b.m;
}
template<typename T>T qpow(T a,T b,T n,T ans=1){
  for(a%=n;b;b>>=1)b&1&&(ans=1ll*ans*a%n),a=1ll*a*a%n;
  return ans;
}
template<typename T>T inv(T a,T b)
{
  return qpow(a,b-2,b);
}
int C(int n,int m){
  return 1ll*fac[n]*vac[m]%mod*vac[n-m]%mod;
}
signed main(){
  fac[0]=1;
  for(int i=1;i<=100000;i++)fac[i]=1ll*fac[i-1]*i%mod;
  vac[0]=1,vac[100000]=inv(fac[100000],mod);
  for(int i=99999;i>=1;i--)vac[i]=1ll*vac[i+1]*(i+1)%mod;
  cin>>t;
  for(int i=1;i<=t;i++)cin>>q[i].n>>q[i].m,q[i].id=i;
  bn=sqrt(100000),sort(q+1,q+t+1,cmp);
  for(int i=1,n=1,m=0;i<=t;i++){
    while(m>q[i].m)now=(now-C(n,m--)+mod)%mod; 
    while(n<q[i].n)now=(now*2ll-C(n++,m)+mod)%mod;
    while(m<q[i].m)now=(now+C(n,++m))%mod; 
    while(n>q[i].n)now=(now+C(--n,m))%mod*500000004ll%mod; 
    ans[q[i].id]=now;
  }
  for(int i=1;i<=t;i++)cout<<ans[i]<<'\n';
  return 0;
}

注意事项

  1. 指针移动的顺序

为了防止出现指针右端点小于左端点的情况,不能随意调换四个循环。设第一步操作左端点,只有三种正确:l--,r--,r++,l++l--,r++,l++,r--l--,r++,r--,l++

  1. 奇偶化排序

当左端点相同时,如果左端点的块为奇数,右端点从小到大排序,否则从大到小排序。这样的话偶数块的询问可以在奇数块询问解决后,\(r\) 指针返回的途中解决。

bool cmp(query a,query b){
  return a.l/bn==b.l/bn?(a.l/bn&1?a.r<b.r:a.r>b.r):a.l<b.l;
}

带修莫队

莫队不只有区间查询,也可支持修改。

P1903

多加一维指针 \(x\) 表示查询之前有多少修改,发现一个修改操作的删除撤销也可以 \(O(1)\),像指针 \(l,r\) 一样移动。

排序方法参照原版莫队,前两个按块编号排序,最后一维按位置排序。

带修莫队属于高维莫队。一般地,设莫队的维数为 \(k\),则前 \(k-1\) 维每一维分别最多移动 \(mB\),共 \(mB(k-1)\) 步,可以视 \(k-1\) 为常数。最后一维中每一次前面的块变动都有可能走一次 \(O(n)\),共 \(O(\frac{n^k}{B^{k-1}})\)。块长 \(\frac{n}{\sqrt[k]{m}}\) 最优,此时复杂度为 \(O(nm^{\frac{k-1}{k}})\)

带修莫队共三维,设 \(n,m\) 同阶,复杂度 \(O(n^\frac{5}{3})\),块长 \(O(n^{\frac 2 3})\)

#include<bits/stdc++.h>
using namespace std;
int a[133338],n,bn,m,cnt1,cnt2,cnt[1000005],ans[133338],now;
struct query{
  int l,r,x,id;
}q[133338];
bool cmp(query a,query b){
  return a.l/bn==b.l/bn?(a.r/bn==b.r/bn?a.x<b.x:a.r<b.r):a.l<b.l;
}
struct modify{
  int p,c;
}c[133338];
void add(int x){
  now+=!cnt[x],cnt[x]++;
}
void del(int x){
  cnt[x]--,now-=!cnt[x];
}
int main(){
  cin>>n>>m,bn=pow(n,2.0/3);
  for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1,x,y;i<=m;i++){
    char opt;
    cin>>opt>>x>>y;
    if(opt=='Q')q[++cnt1].l=x,q[cnt1].r=y,q[cnt1].x=cnt2,q[cnt1].id=cnt1;
    else c[++cnt2].p=x,c[cnt2].c=y;
  }
  sort(q+1,q+cnt1+1,cmp);
  for(int i=1,l=1,r=0,x=0;i<=cnt1;i++){
    while(l>q[i].l)add(a[--l]);
    while(r<q[i].r)add(a[++r]);
    while(l<q[i].l)del(a[l++]);
    while(r>q[i].r)del(a[r--]);
    while(x<q[i].x){
      x++;
      if(l<=c[x].p&&c[x].p<=r)del(a[c[x].p]),add(c[x].c);
      swap(a[c[x].p],c[x].c);
    }
    while(x>q[i].x){
      if(l<=c[x].p&&c[x].p<=r)del(a[c[x].p]),add(c[x].c);
      swap(a[c[x].p],c[x].c),x--;
    }
    ans[q[i].id]=now;
  }
  for(int i=1;i<=cnt1;i++)cout<<ans[i]<<'\n';
  return 0;
}

树上莫队

括号序树上莫队

记录括号序,设节点的两次出现为 \(a,b\)

不妨设 \(a_u<a_v\)。若 \(u\)\(v\) 的祖先,那么 \(u\rightsquigarrow v\) 在括号序上为 \([a_u,a_v]\)。否则 \(u\rightsquigarrow v\) 对应 \([b_u,a_v]\)。这个区间不包含 \(lca(u,v)\),需要额外加上。

如果一个点在区间内出现两次,说明这个点进了又出了,不在路径上。那么在路径上的点会出现一次,不在的点会出现两次,维护一个数组表示节点是否在路径上。

SP10707

#include<bits/stdc++.h>
using namespace std;
template<typename T>void in(T &a)
{
  T ans=0;
  char c=getchar();
  for(;c<'0'||c>'9';c=getchar());
  for(;c>='0'&&c<='9';c=getchar())ans=ans*10+c-'0';
  a=ans;
}
template<typename T,typename... Args>void in(T &a,Args&...args)
{
  in(a),in(args...);
}
int n,m,bn,a[40005],cnt[40005],ans[100005],lg[40005],dep[40005],f[40005][20],dfn[80005],vis1[40005],vis2[40005],num,now;
vector<int>e[40005];
bool vis[40005];
struct query{
  int nl,nr,l,r,id;
  bool f;
}q[100005];
bool cmp(query a,query b){
  return a.l/bn==b.l/bn?(a.l/bn&1?a.r<b.r:a.r>b.r):a.l<b.l;
}
void rebuild(int n,int a[],int cnt=0){
  int temp[n+5];
  for(int i=1;i<=n;i++)temp[i]=a[i];
  sort(temp+1,temp+n+1),cnt=unique(temp+1,temp+n+1)-temp-1;
  for(int i=1;i<=n;i++)a[i]=lower_bound(temp+1,temp+cnt+1,a[i])-temp;
}
void dfs(int pos,int fa,vector<int>e[]){
  f[pos][0]=fa,dep[pos]=dep[fa]+1,dfn[++num]=pos,vis1[pos]=num;
  for(int i=1;i<=lg[dep[pos]];i++)f[pos][i]=f[f[pos][i-1]][i-1];
  for(int i=0;i<e[pos].size();i++)if(e[pos][i]!=fa)dfs(e[pos][i],pos,e);
  dfn[++num]=pos,vis2[pos]=num;
}
void init(int n,int s,vector<int>e[]){
  for(int i=2;i<=n;i++)lg[i]=lg[i>>1]+1;
  dfs(s,0,e);
}
void add(int x){
  now+=!cnt[x]++;
}
void del(int x){
  now-=!--cnt[x];
}
void modify(int x){
  (vis[x]^=1)?add(a[x]):del(a[x]);
}
int lca(int u,int v){
  if(dep[u]<dep[v])swap(u,v);
  while(dep[u]>dep[v])u=f[u][lg[dep[u]-dep[v]]];
  if(u==v)return u;
  for(int i=lg[dep[u]];i>=0;i--)if(f[u][i]!=f[v][i])u=f[u][i],v=f[v][i];
  return f[u][0];
}
int main(){
  in(n,m),bn=sqrt(2*n);
  for(int i=1;i<=n;i++)in(a[i]);
  for(int i=1,x,y;i<n;i++)in(x,y),e[x].push_back(y),e[y].push_back(x);
  rebuild(n,a),init(n,1,e);
  for(int i=1;i<=m;i++){
    in(q[i].nl,q[i].nr),q[i].l=q[i].nl,q[i].r=q[i].nr,q[i].id=i;
    if(vis1[q[i].l]>vis1[q[i].r])swap(q[i].l,q[i].r);
    if(lca(q[i].l,q[i].r)==q[i].l)q[i].l=vis1[q[i].l],q[i].r=vis1[q[i].r],q[i].f=0;
    else q[i].l=vis2[q[i].l],q[i].r=vis1[q[i].r],q[i].f=1;
  }
  sort(q+1,q+m+1,cmp);
  for(int i=1,l=1,r=0;i<=m;i++){
    while(l>q[i].l)modify(dfn[--l]);
    while(r<q[i].r)modify(dfn[++r]);
    while(l<q[i].l)modify(dfn[l++]);
    while(r>q[i].r)modify(dfn[r--]);
    ans[q[i].id]=now;
    if(q[i].f)modify(lca(q[i].nl,q[i].nr)),ans[q[i].id]=now,modify(lca(q[i].nl,q[i].nr));
  }
  for(int i=1;i<=m;i++)cout<<ans[i]<<'\n';
  return 0;
}

树分块

如果直接在树上暴力分块,首先需要有一种树分块保证块大小和块直径。

王室联邦分块法:

维护一个栈,当节点访问结束时入栈。搜完某棵子树时,如果栈顶到父节点的距离大于 \(B\) 就一直出栈到父节点。最后剩下的节点放进最后一块。

正确性证明:前面的子树最多剩 \(B-1\) 个,新的子树最多剩 \(B-1\) 个,最多 \(2B-2\) 个。最后一块最多 \(3B-3\) 个。

这样有一些其他很好的性质:出栈时钦定当前节点为这一块的关键点,块内每条路径上的点除了关键点都在块内。这也保证了直径长。

区间移动时,将 \(u,v\) 沿着树上路径移动到 \(u',v'\),可以变为将 \(u,u'\)\(v,v'\) 暴力跳至 LCA。发现在跳的过程中只经过一次的节点在最终的路径上,可以维护一个 vis。另外要额外计算 \(\operatorname{lca}(u,u'),\operatorname{lca}(v,v')\)

#include<bits/stdc++.h>
using namespace std;
int n,m,bn,a[40005],lg[40005],dep[40005],f[40005][20],st[40005],top,rt[40005],b[40005],num,ans[100005],cnt[40005],now;
bool vis[40005];
vector<int>e[40005];
struct query{
  int u,v,id;
}q[100005];
bool cmp(query x,query y){
  return b[x.u]==b[y.u]?b[x.v]<b[y.v]:b[x.u]<b[y.u];
}
void rebuild(int n,int a[],int cnt=0){
  int temp[n+5];
  for(int i=1;i<=n;i++)temp[i]=a[i];
  sort(temp+1,temp+n+1),cnt=unique(temp+1,temp+n+1)-temp-1;
  for(int i=1;i<=n;i++)a[i]=lower_bound(temp+1,temp+cnt+1,a[i])-temp;
}
void dfs(int pos,int fa,vector<int>e[]){
  f[pos][0]=fa,dep[pos]=dep[fa]+1;
  for(int i=1;i<=lg[dep[pos]]+1;i++)f[pos][i]=f[f[pos][i-1]][i-1];
  for(int i=0;i<e[pos].size();i++)if(e[pos][i]!=fa)dfs(e[pos][i],pos,e);
}
void init(int n,int s,vector<int>e[]){
  for(int i=2;i<=n;i++)lg[i]=lg[i>>1]+1;
  dfs(s,0,e);
}
int lca(int u,int v){
  if(dep[u]<dep[v])swap(u,v);
  while(dep[u]>dep[v])u=f[u][lg[dep[u]-dep[v]]];
  if(u==v)return u;
  for(int i=lg[dep[u]];i>=0;i--)if(f[u][i]!=f[v][i])u=f[u][i],v=f[v][i];
  return f[u][0];
}
void dfs1(int pos,int fa){
  int temp=top;
  for(int i=0;i<e[pos].size();i++){
    if(e[pos][i]==fa)continue;
    dfs1(e[pos][i],pos);
    if(top-temp>=bn){
      rt[++num]=pos;
      while(top>temp)b[st[top--]]=num;
    }
  }
  st[++top]=pos;
}
void add(int x){
  now+=!cnt[x]++;
}
void del(int x){
  now-=!--cnt[x];
}
void modify(int x){
  (vis[x]^=1)?add(a[x]):del(a[x]);
}
void move(int u,int v){
  if(dep[u]<dep[v])swap(u,v);
  while(dep[u]>dep[v])modify(u),u=f[u][0];
  while(u!=v)modify(u),modify(v),u=f[u][0],v=f[v][0];
}
int main(){
  cin>>n>>m,bn=sqrt(n);
  for(int i=1;i<=n;i++)cin>>a[i];
  for(int i=1,u,v;i<n;i++)cin>>u>>v,e[u].push_back(v),e[v].push_back(u);
  rebuild(n,a),init(n,1,e),dfs1(1,0);
  if(!num)rt[++num]=1;
  while(top)b[st[top--]]=num;
  for(int i=1;i<=m;i++)cin>>q[i].u>>q[i].v,q[i].id=i;
  sort(q+1,q+m+1,cmp),modify(1);
  for(int i=1,u=1,v=1;i<=m;i++)modify(lca(u,v)),move(u,q[i].u),move(v,q[i].v),u=q[i].u,v=q[i].v,modify(lca(u,v)),ans[q[i].id]=now;
  for(int i=1;i<=m;i++)cout<<ans[i]<<'\n'; 
  return 0;
}

回滚莫队

莫队的指针有增和减。有一些问题容易增加,但不容易减少,这时可以使用回滚莫队。

首先按照莫队的方式排序(不可奇偶排序)。此时,左端点所在块相同时,右端点是只增加的。

当左端点的块变动时,将左右指针都拉回至当前块的右端点并清空所有信息。对于每个询问,先把右指针移到这个询问的右端点。此时把答案和信息中需要修改的位置暂存下来,移动左指针解决当前询问,然后用暂存的信息把左指针拉回到当前块的右端点。对于左右端点在同一块的询问暴力。

AT_joisc2014_c

发现指针增加时,直接对答案取 \(\max\) 即可,但是难以减少,考虑回滚莫队。

#include<bits/stdc++.h>
using namespace std;
int n,m,bn,a[200005],bp[200005],bl[200005],br[200005],cnt1[200005],cnt2[200005],ans[200005],now;
struct query{
  int l,r,id;
}q[200005];
bool cmp(query a,query b){
  return bp[a.l]==bp[b.l]?a.r>b.r:bp[a.l]<bp[b.l];
}
void add(int x){
  if(!--cnt1[x])now=min(now,x);
}
int main(){
  cin>>n>>m,bn=sqrt(n);
  for(int i=1;i<=n;i++)cin>>a[i];
  for(int i=1;i<=m;i++)cin>>q[i].l>>q[i].r,q[i].id=i;
  for(int i=1;i<=bn;i++)bl[i]=n/bn*(i-1)+1,br[i]=n/bn*i;
  br[bn]=n;
  for(int i=1;i<=bn;i++)for(int j=bl[i];j<=br[i];j++)bp[j]=i;
  sort(q+1,q+m+1,cmp);
  for(int p=1,i=1,l=1,r=0;p<=bn;p++){
    for(int j=1;j<=n;j++)cnt1[a[j]]=0;
    now=0,r=n;
    for(int j=bl[p];j<=r;j++)cnt1[a[j]]++;
    while(cnt1[now])now++;
    for(;bp[q[i].l]==p;i++){
      if(bp[q[i].r]==p){
        for(int j=q[i].l;j<=q[i].r;j++)cnt2[a[j]]++;
        while(cnt2[ans[q[i].id]])ans[q[i].id]++;
        for(int j=q[i].l;j<=q[i].r;j++)cnt2[a[j]]=0;
        continue;
      }
      l=bl[p];
      while(r>q[i].r)add(a[r--]);
      long long temp=now;
      while(l<q[i].l)add(a[l++]);
      ans[q[i].id]=now,now=temp;
      while(l>bl[p])cnt1[a[--l]]++;
    }
  }
  for(int i=1;i<=m;i++)cout<<ans[i]<<'\n';
  return 0;
}

P4137

这题刚好相反,\(\operatorname{mex}\) 容易减少,难增加。类似地,每次把左端点拉到块的左端点,右端点拉到 \(n\),同时更改桶即可。

#include<bits/stdc++.h>
using namespace std;
int n,m,bn,a[200005],bp[200005],bl[200005],br[200005],cnt[200005],ans[200005],now;
struct query{
  int l,r,id;
}q[200005];
bool cmp(query a,query b){
  return bp[a.l]==bp[b.l]?a.r>b.r:bp[a.l]<bp[b.l];
}
void del(int x){
  if(!--cnt[x])now=min(now,x);
}
int main(){
  cin>>n>>m,bn=sqrt(n);
  for(int i=1;i<=n;i++)cin>>a[i];
  for(int i=1;i<=m;i++)cin>>q[i].l>>q[i].r,q[i].id=i;
  for(int i=1;i<=bn;i++)bl[i]=n/bn*(i-1)+1,br[i]=n/bn*i;
  br[bn]=n;
  for(int i=1;i<=bn;i++)for(int j=bl[i];j<=br[i];j++)bp[j]=i;
  sort(q+1,q+m+1,cmp);
  for(int p=1,i=1,l=1,r=0;p<=bn;p++){
    for(int j=1;j<=n;j++)cnt[a[j]]=0;
    now=0,r=n;
    for(int j=bl[p];j<=r;j++)cnt[a[j]]++;
    while(cnt[now])now++;
    for(;bp[q[i].l]==p;i++){
      l=bl[p];
      while(r>q[i].r)del(a[r--]);
      long long temp=now;
      while(l<q[i].l)del(a[l++]);
      ans[q[i].id]=now,now=temp;
      while(l>bl[p])cnt[a[--l]]++;
    }
  }
  for(int i=1;i<=m;i++)cout<<ans[i]<<'\n';
  return 0;
}

莫队的在线化改造

有一种分块的套路可以在线解决莫队题。

由于在线,不能通过一个区间转移到另一个区间,可以预处理一些区间的信息转移到查询。

比如预处理 \(ans_{i,j},sum_{i,j}\) 表示 \(i\sim j\) 块的答案与前 \(i\)\(j\) 的出现次数,即桶的前缀和。这就处理了端点在块端点上的所有查询。询问时先查整块答案的表,再用 \(sum\) 更新答案。

优点是这样做可以在线,且顺便解决了回滚莫队的问题,缺点是维护信息需要具有可减性,且空间是根号的。

P4168

#include<bits/stdc++.h>
using namespace std;
int n,m,a[100005],c[100005],bn,b[100005],bl[100005],br[100005],ans[322][322],sum[322][100005],cnt[100005];
void rebuild(int n,int a[],int cnt=0){
  int temp[n+5];
  for(int i=1;i<=n;i++)temp[i]=a[i];
  sort(temp+1,temp+n+1),cnt=unique(temp+1,temp+n+1)-temp-1;
  for(int i=1,t;i<=n;i++)t=lower_bound(temp+1,temp+cnt+1,a[i])-temp,c[t]=a[i],a[i]=t;
}
void build(int n,int a[]){
  bn=sqrt(n);
  for(int i=1;i<=bn;i++)bl[i]=n/bn*(i-1)+1,br[i]=n/bn*i;
  br[bn]=n;
  for(int i=1;i<=bn;i++)for(int j=bl[i];j<=br[i];j++)b[j]=i;
  for(int i=1;i<=bn;i++){
    for(int j=1;j<=br[i];j++)sum[i][a[j]]++;
    for(int j=i;j<=bn;j++){
      ans[i][j]=ans[i][j-1];
      for(int k=bl[j];k<=br[j];k++){
        cnt[a[k]]++;
        if(cnt[a[k]]>cnt[ans[i][j]]||(cnt[a[k]]==cnt[ans[i][j]]&&a[k]<ans[i][j]))ans[i][j]=a[k];
      }
    }
    memset(cnt,0,sizeof(cnt));
  }
}
int query(int l,int r,int t=100001){
  if(b[l]==b[r]){
    for(int i=l;i<=r;i++){
      cnt[a[i]]++;
      if(cnt[a[i]]>cnt[t]||(cnt[a[i]]==cnt[t]&&a[i]<t))t=a[i];
    }
    for(int i=l;i<=r;i++)cnt[a[i]]--;
  }
  else{
    t=ans[b[l]+1][b[r]-1];
    for(int i=l;i<=br[b[l]];i++){
      cnt[a[i]]++;
      if(cnt[a[i]]+sum[b[r]-1][a[i]]-sum[b[l]][a[i]]>cnt[t]+sum[b[r]-1][t]-sum[b[l]][t]||(cnt[a[i]]+sum[b[r]-1][a[i]]-sum[b[l]][a[i]]==cnt[t]+sum[b[r]-1][t]-sum[b[l]][t]&&a[i]<t))t=a[i];
    }
    for(int i=bl[b[r]];i<=r;i++){
      cnt[a[i]]++;
      if(cnt[a[i]]+sum[b[r]-1][a[i]]-sum[b[l]][a[i]]>cnt[t]+sum[b[r]-1][t]-sum[b[l]][t]||(cnt[a[i]]+sum[b[r]-1][a[i]]-sum[b[l]][a[i]]==cnt[t]+sum[b[r]-1][t]-sum[b[l]][t]&&a[i]<t))t=a[i];
    }
    for(int i=l;i<=br[b[l]];i++)cnt[a[i]]--;
    for(int i=bl[b[r]];i<=r;i++)cnt[a[i]]--;
  }
  return t;
}
int main(){
  cin>>n>>m;
  for(int i=1;i<=n;i++)cin>>a[i];
  rebuild(n,a),build(n,a);
  for(int i=1,l,r,x=0;i<=m;i++){
    cin>>l>>r,l=(l+x-1)%n+1,r=(r+x-1)%n+1;
    if(l>r)swap(l,r);
    cout<<(x=c[query(l,r)])<<'\n';
  }
  return 0;
}

[[数据结构]]

posted @ 2024-03-01 09:30  lgh_2009  阅读(13)  评论(0)    收藏  举报