[关闭]
@xiaoziyao 2020-06-20T21:53:34.000000Z 字数 4441 阅读 1245

P5906 【模板】回滚莫队&不删除莫队

解题报告


P5906 【模板】回滚莫队&不删除莫队解题报告:

更好的阅读体验

题意

题意:给定一个长为的序列次询问,求区间内相同的数的最远间隔距离,注意这里的距离指下标差的绝对值。

数据范围:

分析

这是一道回滚莫队的细节题(大部分OIer的回滚莫队入门题为AT1219 歴史の研究,所以在这里我就没有对回滚莫队的讲解了),肝了很久,如果不会回滚莫队的人可以跳过这篇题解。

在回滚莫队之前,我们可以看一下两个预处理
分块:

  1. t=sqrt(n);
  2. for(i=1;i<=t;i++)
  3. l[i]=r[i-1]+1,r[i]=i*t;
  4. if(r[t]<n)
  5. t++,l[t]=r[t-1]+1,r[t]=n;
  6. for(i=1;i<=t;i++)
  7. for(j=l[i];j<=r[i];j++)
  8. pos[j]=i;

注:这里指块数,指块的左边界,指块的右边界,指当前位置所在的块。

离散化:

  1. struct number{
  2. int pos,val;
  3. }num[maxn];
  4. inline int cmp1(number a,number b){
  5. return a.val<b.val;
  6. }
  7. for(i=1;i<=n;i++)
  8. scanf("%d",&num[i].val),num[i].pos=i;
  9. sort(num+1,num+1+n,cmp1);
  10. tot=0;
  11. for(i=1;i<=n;i++){
  12. if(i==1||num[i-1].val!=num[i].val)
  13. tot++;
  14. a[num[i].pos]=tot;
  15. }

这里的离散化我写的方法有点不一样,但意思很容易懂,就不赘述了(是离散化的数组,是离散化后的数组,是帮助离散化的计数器)。

然后是函数,即暴力在区间中加入这个数,更新答案:

  1. inline void add(int x){
  2. st[a[x]]=min(st[a[x]],x),ed[a[x]]=max(ed[a[x]],x);
  3. now=max(now,ed[a[x]]-st[a[x]]);
  4. }

我们按照回滚莫队的板子,可以把莫队拆成四个部分:
1.在同一个块中的暴力:这个很容易写:

  1. if(pos[qx]==pos[qy]){
  2. for(j=qx;j<=qy;j++)
  3. tmpst[a[j]]=inf,tmped[a[j]]=-inf;
  4. for(j=qx;j<=qy;j++){
  5. tmpst[a[j]]=min(tmpst[a[j]],j),tmped[a[j]]=max(tmped[a[j]],j);
  6. tmpn=max(tmpn,tmped[a[j]]-tmpst[a[j]]);
  7. }
  8. ans[qi]=tmpn;
  9. continue;
  10. }

2.换块:这里的换块可以直接将空区间放在的位置,即当前询问的左边界所在块的右边界,记得要把数组置极大值,数组置极小值,因为换块操作只有次,因此暴力清零总体是的(记录每个数字在区间中开始的位置,记录结束的位置)。

  1. if(pos[qx]!=lst){
  2. x=r[pos[qx]]+1,y=r[pos[qx]];
  3. now=0,lst=pos[qx];
  4. for(j=1;j<=tot;j++)
  5. st[j]=inf,ed[j]=-inf;
  6. }

3.右区间扩张,很容易写出代码:

  1. while(y<qy)
  2. y++,add(y);

4.记录当前的答案,更新当前区间答案,再用之前的答案覆盖当前的答案(是记录左右区间的数组,记录答案,记录之前的左区间),但是这个时间复杂度有点高,因为清零操作最劣,因此复杂度会高达

  1. tmpn=now,tmpx=x;
  2. for(j=qx;j<=qy;j++)
  3. tmpst[a[j]]=st[a[j]],tmped[a[j]]=ed[a[j]];
  4. while(x>qx)
  5. x--,add(x);
  6. ans[qi]=now,now=tmpn,x=tmpx;
  7. for(j=qx;j<=qy;j++)
  8. st[a[j]]=tmpst[a[j]],ed[a[j]]=tmped[a[j]];

然后你就可以获得的高分!

考虑优化,发现覆盖数组太麻烦了,直接在上修改可以省去一个循环(但只能优化一些常数)。

我们将上面的代码改为:

  1. while(y<qy)
  2. y++,add(y);
  3. tmpn=now,tmpx=x;
  4. for(j=qx;j<=qy;j++)
  5. tmpst[a[j]]=st[a[j]],tmped[a[j]]=ed[a[j]];
  6. while(x>qx)
  7. x--,tmpadd(x);
  8. ans[qi]=now,now=tmpn,x=tmpx;

然后增加一个函数:

  1. inline void tmpadd(int x){
  2. tmpst[a[x]]=min(tmpst[a[x]],x),tmped[a[x]]=max(tmped[a[x]],x);
  3. now=max(now,tmped[a[x]]-tmpst[a[x]]);
  4. }

分数:

我们发现时间复杂度的瓶颈还是在记录上,我们会记录很多不会修改到的信息,因此可以考虑一个小:时间戳+数组优化,这个优化可以适用于很多重置数组为时间复杂度瓶颈的题目。

具体操作:每次循环开始时加一(指时间戳),然后直接删去上面代码的循环置部分,继续修改函数:

  1. inline void tmpadd(int x){
  2. if(vis[a[x]]!=stp)
  3. vis[a[x]]=stp,tmpst[a[x]]=st[a[x]],tmped[a[x]]=ed[a[x]];
  4. tmpst[a[x]]=min(tmpst[a[x]],x),tmped[a[x]]=max(tmped[a[x]],x);
  5. now=max(now,tmped[a[x]]-tmpst[a[x]]);
  6. }

大意就是如果这个点在当前时间戳还没有访问过,就给赋值,并记录数组,由于每次回滚都会到当前块的右边界加一位置,因此的距离最多是,因此每次询问最多进行操作,这样的赋值就变为了,即整体赋值的复杂度降到了

总体复杂度(这里默认同阶,事实也如此),可以通过本题。

代码

  1. #include<stdio.h>
  2. #include<math.h>
  3. #include<algorithm>
  4. #define inf 1000000000
  5. using namespace std;
  6. const int maxn=200005,maxm=200005;
  7. int i,j,k,m,n,t,tot,x,y,now,lst,stp;
  8. int a[maxn],ans[maxm],l[maxn],r[maxn],pos[maxn],st[maxn],ed[maxn],tmpst[maxn],tmped[maxn],vis[maxn];
  9. struct number{
  10. int pos,val;
  11. }num[maxn];
  12. struct question{
  13. int x,y,id;
  14. }q[maxm];
  15. inline int cmp1(number a,number b){
  16. return a.val<b.val;
  17. }
  18. inline int cmp2(question a,question b){
  19. return pos[a.x]^pos[b.x]? a.x<b.x:a.y<b.y;
  20. }
  21. inline void add(int x){
  22. st[a[x]]=min(st[a[x]],x),ed[a[x]]=max(ed[a[x]],x);
  23. now=max(now,ed[a[x]]-st[a[x]]);
  24. }
  25. inline void tmpadd(int x){
  26. if(vis[a[x]]!=stp)
  27. vis[a[x]]=stp,tmpst[a[x]]=st[a[x]],tmped[a[x]]=ed[a[x]];
  28. tmpst[a[x]]=min(tmpst[a[x]],x),tmped[a[x]]=max(tmped[a[x]],x);
  29. now=max(now,tmped[a[x]]-tmpst[a[x]]);
  30. }
  31. int main(){
  32. scanf("%d",&n);
  33. t=sqrt(n);
  34. for(i=1;i<=t;i++)
  35. l[i]=r[i-1]+1,r[i]=i*t;
  36. if(r[t]<n)
  37. t++,l[t]=r[t-1]+1,r[t]=n;
  38. for(i=1;i<=t;i++)
  39. for(j=l[i];j<=r[i];j++)
  40. pos[j]=i;
  41. for(i=1;i<=n;i++)
  42. scanf("%d",&num[i].val),num[i].pos=i;
  43. sort(num+1,num+1+n,cmp1);
  44. tot=0;
  45. for(i=1;i<=n;i++){
  46. if(i==1||num[i-1].val!=num[i].val)
  47. tot++;
  48. a[num[i].pos]=tot;
  49. }
  50. scanf("%d",&m);
  51. for(i=1;i<=m;i++)
  52. scanf("%d%d",&q[i].x,&q[i].y),q[i].id=i;
  53. sort(q+1,q+1+m,cmp2);
  54. x=1,y=0,now=0,lst=0;
  55. for(i=1;i<=m;i++){
  56. stp++;
  57. int qx=q[i].x,qy=q[i].y,qi=q[i].id,tmpn=0,tmpx;
  58. if(pos[qx]==pos[qy]){
  59. for(j=qx;j<=qy;j++)
  60. tmpst[a[j]]=inf,tmped[a[j]]=-inf;
  61. for(j=qx;j<=qy;j++){
  62. tmpst[a[j]]=min(tmpst[a[j]],j),tmped[a[j]]=max(tmped[a[j]],j);
  63. tmpn=max(tmpn,tmped[a[j]]-tmpst[a[j]]);
  64. }
  65. ans[qi]=tmpn;
  66. continue;
  67. }
  68. if(pos[qx]!=lst){
  69. x=r[pos[qx]]+1,y=r[pos[qx]];
  70. now=0,lst=pos[qx];
  71. for(j=1;j<=tot;j++)
  72. st[j]=inf,ed[j]=-inf;
  73. }
  74. while(y<qy)
  75. y++,add(y);
  76. tmpn=now,tmpx=x;
  77. while(x>qx)
  78. x--,tmpadd(x);
  79. ans[qi]=now,now=tmpn,x=tmpx;
  80. }
  81. for(i=1;i<=m;i++)
  82. printf("%d\n",ans[i]);
  83. return 0;
  84. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注