(就是乱搞)c++
咱们考虑一个问题:区间修改单点查询,n,m<=1e5;
算法
那么咱们能够怎么解决这个问题呢?数组
线段树!树状数组!数据结构
分块~!ide
分块是何物呢:是一种基于暴力的算法(优雅的暴力嗷)spa
咱们考虑以下一种玄学方法:3d
将整个序列分为几大块,维护每一个大块的和,单点修改显然能够O(1)实现;code
那么区间查询怎么办呢?假设查询区间是[L,R],那么能够确定的是,若是[L,R]内包含大块,咱们只要加上大块的和就能够惹,至于不在大块内的数,咱们能够暴力求和嘛qwq;blog
虽然这样听起来也会令你T到升天,可是老是比n^2快了一些对吧;排序
那么这么作的时间复杂度究竟是多少呢?咱们来分析一下;
首先,假设咱们把整个序列长度为N,分红了M块,那么每次查询的复杂度为:O(M+N/M);
这个复杂度到底在什么范围内呢?
咱们根据均值(基本)不等式能够得出(M+N/M)<=2√(N);在M==N/M时取到最小值;
也就是嗦,当M=√N时,总复杂度最小,为√N;
那么解决问题总复杂就是O(N+M√N)的,怎么样,是否是还能够接受;
咱们来看另外一个问题:
区间加,区间小于k的个数,n,m<=1e5;
线段树玩家已暴毙
咱们仍然能够用分块来求解:
先将每一个块进行块内排序,复杂度nlog√n;
区间小于k个个数求法就很显然了:对于不在整块内的部分,暴力统计;整块内的部分二分查找,每次处理复杂度√Nlog√N;
区间加法怎么办呢?
对于整块,区间加法显然不破坏大小关系,直接打上一个标记便可;
对于散块,因为散块最多有2块,显然咱们能够直接打破,暴力重构,从新排序,复杂度√Nlog√N;
总复杂度O(Nlog√N+M√Nlog√N);
发现了吗,因为分块算法牺牲了部分时间复杂度,因此能够处理的信息更加灵活,相比与传统区间数据结构,分块算法能够处理
这里借用黄学长的分块入门系列qwq;
分块入门1:
区间加法,单点查询;
上面讲过惹qwq;
#include<bits/stdc++.h> using namespace std; #define int long long inline int read() { int x=0,f=1; char ch; for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar()); if(ch=='-') f=0,ch=getchar(); while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();} return f?x:-x; } int n,cnt; int a[100010]; int bel[100010],tag[100010]; inline void update(int l,int r,int k) { int lx=bel[l],rx=bel[r]; for(int i=l;bel[i]==lx&&i<=r;++i) a[i]+=k; if(lx==rx) return ; for(int i=r;bel[i]==rx;--i) a[i]+=k; for(int i=lx+1;i<rx;++i) tag[i]+=k; } signed main() { n=read(); cnt=sqrt(n); for(int i=1;i<=n;++i) { a[i]=read(); bel[i]=(i-1)/cnt+1; } for(int t,x,y,z,i=1;i<=n;++i) { t=read(),x=read(),y=read(),z=read(); if(!t) update(x,y,z); else printf("%lld\n",a[y]+tag[bel[y]]); } return 0; }
分块入门2:
区间加法,区间小于k个数;
#include<bits/stdc++.h> using namespace std; #define int long long inline int read() { int x=0,f=1; char ch; for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar()); if(ch=='-') f=0,ch=getchar(); while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();} return f?x:-x; } int n,cnt,sum; int a[100010],bel[100010],tag[100010]; int c[500][500]; inline void sor(int x) { memset(c[x],0,sizeof(c[x])); for(int i=(x-1)*cnt+1;i<=min(n,x*cnt);++i) { c[x][++c[x][0]]=a[i]; } sort(c[x]+1,c[x]+c[x][0]+1); } inline void update(int x,int y,int k) { int lx=bel[x],rx=bel[y]; for(int i=x;bel[i]==lx&&i<=y;++i) a[i]+=k; sor(lx); if(lx==rx) return ; for(int i=y;bel[i]==rx;--i) a[i]+=k; sor(rx); for(int i=lx+1;i<rx;++i) tag[i]+=k; } inline int query(int x,int y,int k) { int res=0; int lx=bel[x],rx=bel[y]; for(int i=x;bel[i]==lx&&i<=y;++i) { if(a[i]+tag[lx]<k) ++res; } if(lx==rx) return res; for(int i=y;bel[i]==rx;--i) { if(a[i]+tag[rx]<k) ++res; } for(int i=lx+1;i<rx;++i) { int l=1,r=c[i][0],ans=0; while(l<=r) { int mid=(l+r)>>1; if(c[i][mid]+tag[i]>=k) r=mid-1; else ans=mid,l=mid+1; } res+=ans; } return res; } signed main() { n=read(); cnt=sqrt(n); sum=n/cnt+(n%cnt>0); for(int i=1;i<=n;++i) { a[i]=read(); bel[i]=(i-1)/cnt+1; c[bel[i]][++c[bel[i]][0]]=a[i]; } for(int i=1;i<=sum;++i) { sort(c[i]+1,c[i]+c[i][0]+1); } for(int t,x,y,z,i=1;i<=n;++i) { t=read(),x=read(),y=read(),z=read(); if(!t) update(x,y,z); else printf("%lld\n",query(x,y,z*z)); } return 0; }
分块入门3:
区间加法,区间小于k前驱;
显然能够沿用上一题的方法,块内排序+二分;
#include<bits/stdc++.h> using namespace std; #define int long long inline int read() { int x=0,f=1; char ch; for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar()); if(ch=='-') f=0,ch=getchar(); while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();} return f?x:-x; } int n,cnt,sum; int a[100010],bel[100010],tag[100010]; int c[500][500]; inline void sor(int x) { memset(c[x],0,sizeof(c[x])); for(int i=(x-1)*cnt+1;i<=min(n,x*cnt);++i) c[x][++c[x][0]]=a[i]; sort(c[x]+1,c[x]+c[x][0]+1); } inline void update(int x,int y,int k) { int lx=bel[x],rx=bel[y]; for(int i=x;i<=y&&bel[i]==lx;++i) a[i]+=k; sor(lx); if(lx==rx) return ; for(int i=y;bel[i]==rx;--i) a[i]+=k; sor(rx); for(int i=lx+1;i<rx;++i) tag[i]+=k; } inline int query(int x,int y,int k) { int lx=bel[x],rx=bel[y]; int ans=-1; for(int i=x;i<=y&&bel[i]==lx;++i) { if(a[i]+tag[lx]<k) ans=max(ans,a[i]+tag[lx]); } if(lx==rx) return ans; for(int i=y;bel[i]==rx;--i) { if(a[i]+tag[rx]<k) ans=max(ans,a[i]+tag[rx]); } for(int i=lx+1;i<rx;++i) { int l=1,r=c[i][0]; while(l<=r) { int mid=(l+r)>>1; if(c[i][mid]+tag[i]<k) ans=max(ans,c[i][mid]+tag[i]),l=mid+1; else r=mid-1; } } return ans; } signed main() { n=read(); cnt=sqrt(n); sum=n/cnt+(n%cnt>0); for(int i=1;i<=n;++i) { a[i]=read(); bel[i]=(i-1)/cnt+1; c[bel[i]][++c[bel[i]][0]]=a[i]; } for(int i=1;i<=sum;++i) sort(c[i]+1,c[i]+c[i][0]+1); for(int t,x,y,z,i=1;i<=n;++i) { t=read(),x=read(),y=read(),z=read(); if(!t) update(x,y,z); else printf("%lld\n",query(x,y,z)); } return 0; }
分块入门4:
区间加法,区间求和;
区间求和已经讲过了,至于区间加法,在整块上打上标记表示这一块加上了多少,散块直接暴力添加,修改块和;
#include<bits/stdc++.h> using namespace std; #define int long long inline int read() { int x=0,f=1; char ch; for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar()); if(ch=='-') f=0,ch=getchar(); while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();} return f?x:-x; } int n,cnt; int a[100010],bel[100010],tag[100010]; int sum[100010]; inline void update(int x,int y,int k) { int lx=bel[x],rx=bel[y]; for(int i=x;i<=y&&bel[i]==lx;++i) a[i]+=k,sum[lx]+=k; if(lx==rx) return ; for(int i=y;bel[i]==rx;--i) a[i]+=k,sum[rx]+=k; for(int i=lx+1;i<rx;++i) tag[i]+=k; } inline int query(int x,int y,int lyy) { int lx=bel[x],rx=bel[y],res=0; for(int i=x;i<=y&&bel[i]==lx;++i) res = ( res + a[i] + tag[lx] ) % lyy ; if(lx==rx) return res%lyy; for(int i=y;bel[i]==rx;--i) res = ( res + a[i] + tag[rx] ) % lyy ; for(int i=lx+1;i<rx;++i) res = ( res + sum[i] + ( cnt * tag[i] ) % lyy ) % lyy ; return res%lyy; } signed main() { n=read(); cnt=sqrt(n); for(int i=1;i<=n;++i) { a[i]=read(); bel[i]=(i-1)/cnt+1; sum[bel[i]]+=a[i]; } for(int t,x,y,z,i=1;i<=n;++i) { t=read(),x=read(),y=read(),z=read(); if(!t) update(x,y,z); else printf("%lld\n",query(x,y,z+1)); } return 0; }
分块入门5:
区间求和,区间开方(下取整);
啥啥啥这是啥???区间开方?这是个啥?(我刚看到这个题目心里OS)
不过仔细思考,开方还要下取整,那岂不是1和0怎么开不变咯;
分析每一个数据小于2^31-1,那么一个数最多被开方5次,因此咱们每次只要在对一个区间进行暴力开方后,判断这个区间是否所有变成了0or1,若是是,打上一个标记下次跳过;
因为每一个数只能被开方5次,那么总复杂度O(5*N+M√N);
#include<bits/stdc++.h> using namespace std; #define int long long inline int read() { int x=0,f=1; char ch; for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar()); if(ch=='-') f=0,ch=getchar(); while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();} return f?x:-x; } int n,cnt; int a[100010],bel[100010],tag[100010]; int sum[100010]; inline void sqr(int x) { bool flag=0; for(int i=(x-1)*cnt+1;i<=min(n,cnt*x);++i) { sum[x]-=a[i]; sum[x]+=(int)sqrt(a[i]); a[i]=sqrt(a[i]); if(a[i]>1) flag=1; } if(!flag) tag[x]=1; } inline void update(int x,int y) { int lx=bel[x],rx=bel[y]; for(int i=x;i<=y&&bel[i]==lx;++i) { sum[lx]-=a[i]; sum[lx]+=(int)sqrt(a[i]); a[i]=sqrt(a[i]); } if(lx==rx) return ; for(int i=y;bel[i]==rx;--i) { sum[rx]-=a[i]; sum[rx]+=(int)sqrt(a[i]); a[i]=sqrt(a[i]); } for(int i=lx+1;i<rx;++i) { if(tag[i]) continue; else sqr(i); } } inline int query(int x,int y) { int lx=bel[x],rx=bel[y]; int res=0; for(int i=x;i<=y&&bel[i]==lx;++i) res+=a[i]; if(lx==rx) return res; for(int i=y;bel[i]==rx;--i) res+=a[i]; for(int i=lx+1;i<rx;++i) res+=sum[i]; return res; } signed main() { n=read(); cnt=sqrt(n); for(int i=1;i<=n;++i) { a[i]=read(); bel[i]=(i-1)/cnt+1; sum[bel[i]]+=a[i]; } for(int t,x,y,z,i=1;i<=n;++i) { t=read(),x=read(),y=read(),z=read(); if(!t) update(x,y); else printf("%lld\n",query(x,y)); } return 0; }
分块入门6:
单点插入单点查询,数据随机;
考虑到数据随机,统计块内数字数量,用链表插入便可;
可是有一个问题:数据不随机怎么办?若是每次都往同一个块内插入,复杂度没法保证;
咱们能够设定一个值,(我设为当前序列数量的根号),一旦插入次数超过这个值,将整个序列从新分块,理论复杂度O(M√N+N√M);
#include<bits/stdc++.h> using namespace std; #define int long long inline int read() { int x=0,f=1; char ch; for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar()); if(ch=='-') f=0,ch=getchar(); while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();} return f?x:-x; } int n,cnt,tot; int a[200010],bel[200010],st[200010],ed[200010]; int sum[200010]; int nxt[200010],pre[200010]; inline void miao() { int t=0; memset(sum,0,sizeof(sum)); cnt=sqrt(tot); for(int i=st[1];i;i=nxt[i]) { bel[i]=t/cnt+1; ++sum[bel[i]]; if(bel[i]^bel[pre[i]]) st[bel[i]]=i,ed[bel[i]-1]=pre[i]; ++t; } } inline void update(int x,int y) { int pos=0,now; for(int i=1;;++i) { if(pos+sum[i]>=x) { now=i; break; } pos+=sum[i]; } for(int i=st[now];i!=nxt[ed[now]];i=nxt[i]) { if(++pos==x) { a[++tot]=y; nxt[tot]=i; pre[tot]=pre[i]; nxt[pre[i]]=tot; pre[i]=tot; if(i==st[now]) st[now]=tot; ++sum[now]; break; } } } inline int query(int x) { int pos=0,now; for(int i=1;;++i) { if(pos+sum[i]>=x) { now=i; break; } pos+=sum[i]; } for(int i=st[now];i!=nxt[ed[now]];i=nxt[i]) { if(++pos==x) return a[i]; } } signed main() { n=tot=read(); cnt=sqrt(n); for(int i=1;i<=n;++i) { a[i]=read(); bel[i]=(i-1)/cnt+1; if(bel[i]^bel[i-1]) st[bel[i]]=i,ed[bel[i]-1]=i-1; ++sum[bel[i]]; if(i^n) nxt[i]=i+1; if(i^1) pre[i]=i-1; if(i==n) ed[bel[i]]=i; } for(int t,x,y,z,i=1;i<=n;++i) { t=read(),x=read(),y=read(),z=read(); if(!t) { update(x,y); if(tot%cnt==0) miao(); } else printf("%lld\n",query(y)); } return 0; }
分块入门7:
区间乘法,区间加法,区间求和;(就是线段树2嘛)
考虑两种标记的前后顺序:
若是咱们对于两个同时存在的标记,先加再乘:
假设当前状态为(a+add)*mul;又添加了一组[add2,mul2]标记;
当前状态变为((a+add)*mul+add2)*mul2;
展开((a+add)*mul*mul2+add2*mul2);
展开(a*mul*mul2+add*mul*mul2+add2*mul2);
恢复标记格式:(a+add+(add2/mul))*mul*mul2;
发现了什么?分数!这样可能会丢精度qwq;
改变顺序,先乘后加;
(a*mul+add)添加新标记[add2,mul2];
当前状态变为((a*mul)+add)*mul2+add2;
展开 (a*mul*mul2+add*mul2+add2);
恢复标记格式:(a*mul*mul2)+add*mul2+add2;
完美;
注意细节:在分块中,若是要对散块处理,要先把整个散块的标记所有下放;
#include<bits/stdc++.h> using namespace std; #define int long long inline int read() { int x=0,f=1; char ch; for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar()); if(ch=='-') f=0,ch=getchar(); while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();} return f?x:-x; } const int chx=10007; int n,cnt; int a[100010],bel[100010]; int add[100010],mul[100010]; inline void push_down(int x) { for(int i=(x-1)*cnt+1;i<=min(n,x*cnt);++i) { a[i] = ( a[i] * mul[x] + add[x] ) % chx ; } add[x]=0; mul[x]=1; } inline void upadd(int x,int y,int k) { int lx=bel[x],rx=bel[y]; push_down(lx); for(int i=x;i<=y&&bel[i]==lx;++i) { a[i] = ( a[i] + k ) % chx ; } if(lx==rx) return ; push_down(rx); for(int i=y;bel[i]==rx;--i) { a[i] = ( a[i] + k ) % chx ; } for(int i=lx+1;i<rx;++i) add[i] = ( add[i] + k ) % chx ; } inline void upmul(int x,int y,int k) { int lx=bel[x],rx=bel[y]; push_down(lx); for(int i=x;i<=y&&bel[i]==lx;++i) { a[i] = ( a[i] * k ) % chx ; } if(lx==rx) return ; push_down(rx); for(int i=y;bel[i]==rx;--i) { a[i] = ( a[i] * k ) % chx ; } for(int i=lx+1;i<rx;++i) { add[i] = ( add[i] * k ) % chx ; mul[i] = ( mul[i] * k ) % chx ; } } inline int query(int x) { return (( a[x] * mul[bel[x]] ) % chx + add[bel[x]] ) % chx ; } signed main() { n=read(); cnt=sqrt(n); for(int i=1;i<=n;++i) { a[i]=read()%chx; bel[i]=(i-1)/cnt+1; mul[bel[i]]=1; } for(int t,x,y,z,i=1;i<=n;++i) { t=read(),x=read(),y=read(),z=read(); if(!t) { upadd(x,y,z); } if(t==1) { upmul(x,y,z); } if(t==2) { printf("%lld\n",query(y)); } } return 0; }
分块入门8:
查询一段区间内元素等于k的个数,并将该区间内元素所有改成k;
一个想法是,将整块的所有为同一数字的打上标记,非同一数字:散块暴力,整块块内排序二分查找;
很不幸,他T了……
正解我看了想喷人qwq
对于非同一数字部分暴力统计,同一数字部分整块处理……
复杂度分析:对于原序列的改造:只要用O(n)的复杂度,必定会将原序列所有变成同一数字的;
那么处理不一样数字的代价是什么呢?
考虑每一次操做,最多将两个块变成不一样数字的,这两个块是须要暴力的,也就是说每次操做只会产生2个不一样数字的块,带来√N的复杂度;
总复杂度为O(M√N+N);
#include<bits/stdc++.h> using namespace std; #define int long long inline int read() { int x=0,f=1; char ch; for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar()); if(ch=='-') f=0,ch=getchar(); while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();} return f?x:-x; } int n,cnt; int a[100010],bel[100010],tag[100010]; inline void push_down(int x) { for(int i=(x-1)*cnt+1;i<=min(n,x*cnt);++i) a[i]=tag[x]; tag[x]=-1; } inline int query(int x,int y,int k) { int sum=0; int lx=bel[x],rx=bel[y]; if(tag[lx]!=-1) push_down(lx); for(int i=x;i<=y&&bel[i]==lx;++i) sum+=(a[i]==k),a[i]=k; if(lx==rx) return sum; if(tag[rx]!=-1) push_down(rx); for(int i=y;bel[i]==rx;--i) sum+=(a[i]==k),a[i]=k; for(int i=lx+1;i<rx;++i) { if(tag[i]!=-1) { sum+=(tag[i]==k?cnt:0); } else { for(int j=(i-1)*cnt+1;j<=i*cnt;++j) sum+=(a[j]==k); } tag[i]=k; } return sum; } signed main() { n=read(); cnt=sqrt(n); for(int i=1;i<=n;++i) { a[i]=read(); bel[i]=(i-1)/cnt+1; tag[i]=-1; } for(int x,y,z,i=1;i<=n;++i) { x=read(),y=read(),z=read(); printf("%lld\n",query(x,y,z)); } return 0; }
分块入门9&&洛谷P4168蒲公英:
区间众数查询;
毒瘤来惹QAQ这道题目分享两个方法:
方法一:二分
说点别的,明明最近有一次考试题目就是这道题的阉割版,只取二分部分就是切掉,我竟然没想出来,哭惹哭惹;
有一个引理:有两个数字集合A和B,设A集合的众数为X,那么整个集合的众数必定属于X∪B;
证实:不会,详见陈立杰《区间众数解题报告》;
根据引理,咱们能够知道:只要判断两个散块的数和中间整块的众数就能够找到众数;
枚举散块和整块众数复杂度为O(N),那么咱们须要一个O(1)判断一个数字在区间中出现次数的方法;
很遗憾,没有,可是有logN的;
咱们能够以下处理:
用vector数组存储每一个数字出现的位置,用lower_bound和upper_bound找到这个数字在区间中第一次出现的位置和最后一次出现的位置;
至于整块的众数,能够直接N√N处理出来,详见代码,一看就会qwq;
注意一个问题:在该题目中,因为预处理只要一次,而查询代价较多,咱们能够适当减少块的大小,让每次查询的代价更小;
亲测当块的大小为50的时候跑地飞快;下面放的是洛谷AC代码,LOJ上的题目需略做修改;
#include<bits/stdc++.h> using namespace std; #define int long long inline int read() { int x=0,f=1; char ch; for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar()); if(ch=='-') f=0,ch=getchar(); while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();} return f?x:-x; } int n,m,cnt,tot,kuai,miao; int lx,rx,maxsum,ans,t; int a[100010],bel[100010]; int f[1010][1010]; int val[100010]; int sum[100010]; vector<int> q[100010]; map<int,int> vis; inline void get_num(int x) { memset(sum,0,sizeof(sum)); int maxsum=-1,ans=0; for(int i=(x-1)*cnt+1;i<=n;++i) { ++sum[a[i]]; if(sum[a[i]]>maxsum||(sum[a[i]]>=maxsum&&val[a[i]]<=val[ans])) maxsum=sum[a[i]],ans=a[i]; f[x][bel[i]]=ans; } } inline int ask(int l,int r,int k) { return upper_bound(q[k].begin(),q[k].end(),r)-lower_bound(q[k].begin(),q[k].end(),l); } inline int query(int x,int y) { lx=bel[x],rx=bel[y]; maxsum=0,ans=0; for(int i=x;i<=y&&bel[i]==lx;++i) { t=ask(x,y,a[i]); if(t>maxsum||(t>=maxsum&&val[a[i]]<=val[ans])) maxsum=t,ans=a[i]; } if(lx^rx) { for(int i=y;bel[i]==rx;--i) { t=ask(x,y,a[i]); if(t>maxsum||(t>=maxsum&&val[a[i]]<=val[ans])) maxsum=t,ans=a[i]; } if(lx+1<=rx-1) { t=ask(x,y,f[lx+1][rx-1]); if(t>maxsum||(t>=maxsum&&val[f[lx+1][rx-1]]<=val[ans])) maxsum=t,ans=f[lx+1][rx-1]; } } return ans; } signed main() { n=read(),m=read(); cnt=50; for(int i=1;i<=n;++i) { a[i]=read(); if(!vis[a[i]]) { vis[a[i]]=++tot; val[tot]=a[i]; } a[i]=vis[a[i]]; q[a[i]].push_back(i); bel[i]=(i-1)/cnt+1; } for(int i=1;i<=bel[n];++i) get_num(i); for(int x,y,i=1;i<=m;++i) { x=read(),y=read(); x=(x+miao-1)%n+1,y=(y+miao-1)%n+1; if(x>y) swap(x,y); miao=val[query(x,y)]; printf("%lld\n",miao); } return 0; }
方法二:预处理:
预处理出一个p[ ][ ]数组,p[ i ][ j ]表示前i个块内,j这个数字出现的次数;
表示这个方法也应该是N√N才对,甚至理论复杂度更优秀,可是因为一股神秘力量致使方法一将块大小改成50后快得飞起,这个方法很不幸地慢了一截;
#include<bits/stdc++.h> using namespace std; #define int long long inline int read() { int x=0,f=1; char ch; for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar()); if(ch=='-') f=0,ch=getchar(); while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();} return f?x:-x; } int n,m,cnt,tot,kuai,miao; int lx,rx,maxsum,ans,t; int a[100010],bel[100010]; int f[1010][1010]; int p[500][40010]; int val[100010]; int sum[100010]; int c[100010]; inline void get_num(int x) { memset(sum,0,sizeof(sum)); int maxsum=-1,ans=0; for(int i=(x-1)*cnt+1;i<=n;++i) { ++sum[a[i]]; if(sum[a[i]]>maxsum||(sum[a[i]]>=maxsum&&val[a[i]]<=val[ans])) maxsum=sum[a[i]],ans=a[i]; f[x][bel[i]]=ans; } } inline int query(int x,int y) { lx=bel[x],rx=bel[y]; maxsum=0,ans=0; memset(sum,0,sizeof(sum)); for(int i=x;i<=y&&bel[i]==lx;++i) { ++sum[a[i]]; t=sum[a[i]]+max(p[rx-1][a[i]]-p[lx][a[i]],0ll); if(t>maxsum||(t>=maxsum&&val[a[i]]<=val[ans])) maxsum=t,ans=a[i]; } if(lx^rx) { for(int i=y;bel[i]==rx;--i) { ++sum[a[i]]; t=sum[a[i]]+max(p[rx-1][a[i]]-p[lx][a[i]],0ll); if(t>maxsum||(t>=maxsum&&val[a[i]]<=val[ans])) maxsum=t,ans=a[i]; } if(lx+1<=rx-1) { t=p[rx-1][f[lx+1][rx-1]]-p[lx][f[lx+1][rx-1]]+sum[f[lx+1][rx-1]]; if(t>maxsum||(t>=maxsum&&val[f[lx+1][rx-1]]<=val[ans])) maxsum=t,ans=f[lx+1][rx-1]; } } return ans; } signed main() { n=read(),m=read(); cnt=sqrt(n); for(int i=1;i<=n;++i) { a[i]=read(); bel[i]=(i-1)/cnt+1; c[++c[0]]=a[i]; } sort(c+1,c+c[0]+1); c[0]=unique(c+1,c+c[0]+1)-c-1; for(int i=1;i<=n;++i) { int tyx=lower_bound(c+1,c+c[0]+1,a[i])-c; val[tyx]=a[i]; a[i]=tyx; } for(int i=1;i<=bel[n];++i) { get_num(i); for(int j=(i-1)*cnt+1;j<=min(n,i*cnt);++j) { ++p[i][a[j]]; } for(int j=1;j<=c[0];++j) { p[i][j]+=p[i-1][j]; } } for(int x,y,i=1;i<=m;++i) { x=read(),y=read(); x=(x+miao-1)%n+1,y=(y+miao-1)%n+1; if(x>y) swap(x,y); miao=val[query(x,y)]; printf("%lld\n",miao); } return 0; }