https://www.luogu.com.cn/problem/P5906
莫队好多套路啊。。。建议都学习一下
学习自题解https://www.luogu.com.cn/problem/solution/P5906
回滚莫队的意思大概就是,按l所在块再求枚举询问,相同的一起求答案,l部分的必须每次从当前块的最右边向左拓展到想要的地方,求完当前询问之后,你此时更新出的答案要回滚到l向左移动之前,再求下个询问。
左端点在一个块中的询问处理完之后,我们清空所有最左和最右的数字位置的数组,再处理下个块中的询问。
这样,对于同一个块,右端点是不断增加的,所以当前块右边的ed是不变的,当前块内的数字的ed有可能新出现,就先标记上,然后回退的时候再清掉。
那么一个询问最多就是l移动sqrt(n)次,对于同一个块内所有询问,r最多移动n次,总复杂度还是nsqrt(n)
#include<bits/stdc++.h>
using namespace std;
const int maxl=2e5+10;
int n,m,len,bn,tot;
int a[maxl],num[maxl],ans[maxl];
int bel[maxl],last[maxl],st[maxl],ed[maxl],clr[maxl];
struct que
{
int l,r,id;
inline bool operator < (const que &b)const
{
return (bel[l]!=bel[b.l])?bel[l]<bel[b.l]:r<b.r;
}
}q[maxl];
inline void prework()
{
scanf("%d",&n);
len=sqrt(n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
bel[i]=(i-1)/len+1;num[i]=a[i];
}
bn=bel[n];
sort(num+1,num+1+n);
tot=unique(num+1,num+1+n)-num-1;
for(int i=1;i<=n;i++)
a[i]=lower_bound(num+1,num+1+tot,a[i])-num;
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&q[i].l,&q[i].r);
q[i].id=i;
}
sort(q+1,q+1+m);
}
inline int bf(int l,int r)
{
int mx=0;
for(int i=l;i<=r;i++)
last[a[i]]=0;
for(int i=l;i<=r;i++)
if(!last[a[i]])
last[a[i]]=i;
else
mx=max(mx,i-last[a[i]]);
return mx;
}
inline void mainwork()
{
int ind=1,br,l,r,now,cnt,tmp;
for(int j=1;j<=bn;j++)
{
br=min(n,j*len),l=br+1;r=l-1;
cnt=0;now=0;
while(bel[q[ind].l]==j)
{
if(bel[q[ind].r]==j)
{
ans[q[ind].id]=bf(q[ind].l,q[ind].r);
ind++;
continue;
}
while(r<q[ind].r)
{
r++;
ed[a[r]]=r;
if(!st[a[r]])
st[a[r]]=r,clr[++cnt]=a[r];
now=max(now,r-st[a[r]]);
}
tmp=now;
while(l>q[ind].l)
{
l--;
if(ed[a[l]])
now=max(now,ed[a[l]]-l);
else
ed[a[l]]=l;
}
ans[q[ind].id]=now;
while(l<=br)
{
if(ed[a[l]]==l)
ed[a[l]]=0;
l++;
}
now=tmp;
++ind;
}
for(int i=1;i<=cnt;i++)
st[clr[i]]=ed[clr[i]]=0;
}
}
inline void print()
{
for(int i=1;i<=m;i++)
printf("%d\n",ans[i]);
}
int main()
{
prework();
mainwork();
print();
return 0;
}