一、题目
二、解法
很容易想到普通莫队的解法,为了求最大值,我们需要在跑莫队时维护一个堆,时间复杂度
考虑优化,发现我们只能 处理加入的情况,删除就不能 ,那我们能不能只加入不删除呢?答案是可以的,我们分 块来处理,每个块大小为 ,我们每一个把左端点放在当前块的最右端,右端点为左端点 ,对于块中的每个询问(左端点在块中)我们先跑到右端点,再让左端点从块的右端点出发,跑到询问的左端点,完成这一询问后右端点不变(因为按右端点为第二关键字排序),左端点复位,这样我们就只需要加入操作,时间复杂度 。
还有一个问题,就是一个询问的左右端点都在块内怎么办,可以预处理这些询问,反正也只花 的时间。
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
#define int long long
const int M = 100005;
int read()
{
int x=0,flag=1;char c;
while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*flag;
}
int n,m,block,Max,a[M],b[M],t[M],ans[M];
struct data
{
int l,r,id;
data(int L=0,int R=0,int I=0) : l(L) , r(R) , id(I) {}
bool operator < (const data &B) const
{
if(l/block==B.l/block) return r<B.r;
return l<B.l;
}
}q[M];
void add(int x,int f)
{
t[a[x]]+=f*b[a[x]];//应该加入离散化之前对应的值
Max=max(Max,t[a[x]]);
}
signed main()
{
n=read();m=read();
block=sqrt(n)+1;//块的大小
for(int i=1;i<=n;i++)
a[i]=b[i]=read();
sort(b+1,b+1+n);
int *end=unique(b+1,b+1+n);//去重,返回尾指针
for(int i=1;i<=n;i++)
{
a[i]=lower_bound(b+1,end,a[i])-b;//得到离散化后的值
}
for(int i=1;i<=m;i++)
{
int l=read(),r=read();
q[i]=data(l,r,i);
}
sort(q+1,q+1+m);//莫队排序
for(int i=1;i<=m;i++)
if(q[i].l/block==q[i].r/block)
{//左右端点同在一块,暴力算
for(int j=q[i].l;j<=q[i].r;j++) add(j,1);
ans[q[i].id]=Max;
for(int j=q[i].l;j<=q[i].r;j++) add(j,-1);
Max=0;
}
int l=0,r=0;
for(int i=1;i<=m;i++)
{
while(i<=m && q[i].l/block==q[i].r/block) i++;//跳过已经处理的
if(i>m) break;
int ed=(q[i].l/block+1)*block;//算块的右端点
if(ed>n) ed=n;
if(l!=ed)//到了下一个块,清空重新开始
{
l=ed;r=l-1;
for(int i=1;i<=n;i++) t[i]=0;
Max=0;
}
while(r<q[i].r) add(++r,1);//移动右端点
int tmp=Max;//存现在的答案
while(q[i].l<l) add(--l,1);//移动左端点
ans[q[i].id]=Max;
while(l<ed) add(l++,-1);//左端点复位
Max=tmp;
}
for(int i=1;i<=m;i++)
printf("%lld\n",ans[i]);
}