@xiaoziyao
2020-06-20T13:53:34.000000Z
字数 4441
阅读 1704
解题报告
P5906 【模板】回滚莫队&不删除莫队解题报告:
题意:给定一个长为的序列,次询问,求区间内相同的数的最远间隔距离,注意这里的距离指下标差的绝对值。
数据范围:。
这是一道回滚莫队的细节题(大部分OIer的回滚莫队入门题为AT1219 歴史の研究,所以在这里我就没有对回滚莫队的讲解了),肝了很久,如果不会回滚莫队的人可以跳过这篇题解。
在回滚莫队之前,我们可以看一下两个预处理
分块:
t=sqrt(n);for(i=1;i<=t;i++)l[i]=r[i-1]+1,r[i]=i*t;if(r[t]<n)t++,l[t]=r[t-1]+1,r[t]=n;for(i=1;i<=t;i++)for(j=l[i];j<=r[i];j++)pos[j]=i;
注:这里指块数,指块的左边界,指块的右边界,指当前位置所在的块。
离散化:
struct number{int pos,val;}num[maxn];inline int cmp1(number a,number b){return a.val<b.val;}for(i=1;i<=n;i++)scanf("%d",&num[i].val),num[i].pos=i;sort(num+1,num+1+n,cmp1);tot=0;for(i=1;i<=n;i++){if(i==1||num[i-1].val!=num[i].val)tot++;a[num[i].pos]=tot;}
这里的离散化我写的方法有点不一样,但意思很容易懂,就不赘述了(是离散化的数组,是离散化后的数组,是帮助离散化的计数器)。
然后是函数,即暴力在区间中加入这个数,更新答案:
inline void add(int x){st[a[x]]=min(st[a[x]],x),ed[a[x]]=max(ed[a[x]],x);now=max(now,ed[a[x]]-st[a[x]]);}
我们按照回滚莫队的板子,可以把莫队拆成四个部分:
1.在同一个块中的暴力:这个很容易写:
if(pos[qx]==pos[qy]){for(j=qx;j<=qy;j++)tmpst[a[j]]=inf,tmped[a[j]]=-inf;for(j=qx;j<=qy;j++){tmpst[a[j]]=min(tmpst[a[j]],j),tmped[a[j]]=max(tmped[a[j]],j);tmpn=max(tmpn,tmped[a[j]]-tmpst[a[j]]);}ans[qi]=tmpn;continue;}
2.换块:这里的换块可以直接将空区间放在的位置,即当前询问的左边界所在块的右边界,记得要把数组置极大值,数组置极小值,因为换块操作只有次,因此暴力清零总体是的(记录每个数字在区间中开始的位置,记录结束的位置)。
if(pos[qx]!=lst){x=r[pos[qx]]+1,y=r[pos[qx]];now=0,lst=pos[qx];for(j=1;j<=tot;j++)st[j]=inf,ed[j]=-inf;}
3.右区间扩张,很容易写出代码:
while(y<qy)y++,add(y);
4.记录当前的答案,更新当前区间答案,再用之前的答案覆盖当前的答案(和是记录左右区间的数组,记录答案,记录之前的左区间),但是这个时间复杂度有点高,因为清零操作最劣,因此复杂度会高达。
tmpn=now,tmpx=x;for(j=qx;j<=qy;j++)tmpst[a[j]]=st[a[j]],tmped[a[j]]=ed[a[j]];while(x>qx)x--,add(x);ans[qi]=now,now=tmpn,x=tmpx;for(j=qx;j<=qy;j++)st[a[j]]=tmpst[a[j]],ed[a[j]]=tmped[a[j]];
然后你就可以获得的高分!
考虑优化,发现覆盖数组太麻烦了,直接在与上修改可以省去一个循环(但只能优化一些常数)。
我们将上面的代码改为:
while(y<qy)y++,add(y);tmpn=now,tmpx=x;for(j=qx;j<=qy;j++)tmpst[a[j]]=st[a[j]],tmped[a[j]]=ed[a[j]];while(x>qx)x--,tmpadd(x);ans[qi]=now,now=tmpn,x=tmpx;
然后增加一个函数:
inline void tmpadd(int x){tmpst[a[x]]=min(tmpst[a[x]],x),tmped[a[x]]=max(tmped[a[x]],x);now=max(now,tmped[a[x]]-tmpst[a[x]]);}
分数:。
我们发现时间复杂度的瓶颈还是在记录与上,我们会记录很多不会修改到的信息,因此可以考虑一个小:时间戳+数组优化,这个优化可以适用于很多重置数组为时间复杂度瓶颈的题目。
具体操作:每次循环开始时加一(指时间戳),然后直接删去上面代码的循环置和部分,继续修改函数:
inline void tmpadd(int x){if(vis[a[x]]!=stp)vis[a[x]]=stp,tmpst[a[x]]=st[a[x]],tmped[a[x]]=ed[a[x]];tmpst[a[x]]=min(tmpst[a[x]],x),tmped[a[x]]=max(tmped[a[x]],x);now=max(now,tmped[a[x]]-tmpst[a[x]]);}
大意就是如果这个点在当前时间戳还没有访问过,就给和赋值,并记录数组,由于每次回滚都会到当前块的右边界加一位置,因此到的距离最多是,因此每次询问最多进行次操作,这样和的赋值就变为了,即整体赋值的复杂度降到了。
总体复杂度(这里默认同阶,事实也如此),可以通过本题。
#include<stdio.h>#include<math.h>#include<algorithm>#define inf 1000000000using namespace std;const int maxn=200005,maxm=200005;int i,j,k,m,n,t,tot,x,y,now,lst,stp;int a[maxn],ans[maxm],l[maxn],r[maxn],pos[maxn],st[maxn],ed[maxn],tmpst[maxn],tmped[maxn],vis[maxn];struct number{int pos,val;}num[maxn];struct question{int x,y,id;}q[maxm];inline int cmp1(number a,number b){return a.val<b.val;}inline int cmp2(question a,question b){return pos[a.x]^pos[b.x]? a.x<b.x:a.y<b.y;}inline void add(int x){st[a[x]]=min(st[a[x]],x),ed[a[x]]=max(ed[a[x]],x);now=max(now,ed[a[x]]-st[a[x]]);}inline void tmpadd(int x){if(vis[a[x]]!=stp)vis[a[x]]=stp,tmpst[a[x]]=st[a[x]],tmped[a[x]]=ed[a[x]];tmpst[a[x]]=min(tmpst[a[x]],x),tmped[a[x]]=max(tmped[a[x]],x);now=max(now,tmped[a[x]]-tmpst[a[x]]);}int main(){scanf("%d",&n);t=sqrt(n);for(i=1;i<=t;i++)l[i]=r[i-1]+1,r[i]=i*t;if(r[t]<n)t++,l[t]=r[t-1]+1,r[t]=n;for(i=1;i<=t;i++)for(j=l[i];j<=r[i];j++)pos[j]=i;for(i=1;i<=n;i++)scanf("%d",&num[i].val),num[i].pos=i;sort(num+1,num+1+n,cmp1);tot=0;for(i=1;i<=n;i++){if(i==1||num[i-1].val!=num[i].val)tot++;a[num[i].pos]=tot;}scanf("%d",&m);for(i=1;i<=m;i++)scanf("%d%d",&q[i].x,&q[i].y),q[i].id=i;sort(q+1,q+1+m,cmp2);x=1,y=0,now=0,lst=0;for(i=1;i<=m;i++){stp++;int qx=q[i].x,qy=q[i].y,qi=q[i].id,tmpn=0,tmpx;if(pos[qx]==pos[qy]){for(j=qx;j<=qy;j++)tmpst[a[j]]=inf,tmped[a[j]]=-inf;for(j=qx;j<=qy;j++){tmpst[a[j]]=min(tmpst[a[j]],j),tmped[a[j]]=max(tmped[a[j]],j);tmpn=max(tmpn,tmped[a[j]]-tmpst[a[j]]);}ans[qi]=tmpn;continue;}if(pos[qx]!=lst){x=r[pos[qx]]+1,y=r[pos[qx]];now=0,lst=pos[qx];for(j=1;j<=tot;j++)st[j]=inf,ed[j]=-inf;}while(y<qy)y++,add(y);tmpn=now,tmpx=x;while(x>qx)x--,tmpadd(x);ans[qi]=now,now=tmpn,x=tmpx;}for(i=1;i<=m;i++)printf("%d\n",ans[i]);return 0;}