• 2022杭电多校九 1003-Fast Bubble Sort(倍增+单调栈)


    题目链接:杭电多校九 - Virtual Judge

    题目:

     样例输入:

    1. 1
    2. 10 5
    3. 3 7 9 2 6 4 5 8 10 1
    4. 1 10
    5. 2 6
    6. 7 9
    7. 4 9
    8. 3 3

    样例输出:

    1. 2
    2. 1
    3. 0
    4. 1
    5. 0

    题意:给定任何一个长度为𝑁的数组𝐴 = (𝑎1, 𝑎2, . . . , 𝑎𝑛),令𝐵(𝐴)表示对𝐴进行一次bubble sort循环之后得到的数组。令𝑛𝑢𝑚(𝐴)表示从𝐴到𝐵(𝐴)最少需要移动元素(数组区间循环移位)的次数。给定一个1 − 𝑛的排列𝑃以及𝑞组1 ≤ 𝑙 ≤ 𝑟 ≤ 𝑛,求𝑛𝑢𝑚(𝑃 [𝑙, 𝑟])。

    分析:现在假如我们有t个数a1,a2,……,at,我们对区间[1,t]进行一次循环左移,就相当于先用a1和a2换位,再用a1和a3换位……,再用a1和at换位,这个操作非常像我们冒泡排序的过程,通过分析这个我们就不难发现假如我们要询问num(P[l,r]),那么就先找到一个区间[l,x]进行一次循环左移,其中x是l后面第一个大于a[l]的数的前一个,前提是x不能等于l,也就是说区间[l,x]的长度至少是1,那么我们就可以定义一个不同于数学中的极值点,第i个点为极大值点的充分必要条件就是a[i]>a[i+1]。数学中的极大值点定义还要求a[i]>a[i-1],这里不作要求,那么通过模拟不难发现𝑛𝑢𝑚(𝑃 [𝑙, 𝑟])就是[l,r]中递增极值点的个数。不妨假设递增极值点个数为n,那么这n个递增极值点就是我们的循环左移区间的左边界,这个模拟不难得到,那我们怎么求解一个区间中递增极大值点的个数呢?就是先用一个数组记录每一个位置后面出现的第一个极大值点,不妨记为f[i]吧。一开始写的是用单调栈记录每一个数后面第一个大于他的数的位置,然后倒着进行判断,如果第一个大于他的数的位置是当前数的后一个,那么这种情况就对应着区间长度为1的情况,我们就直接令f[i]=f[i+1],否则f[i]就是i后面第一个大于等于i的数的位置,然后直接暴力跳,但是这样会超时,所以我们就需要对这种方式进行优化,这个时候我们就会想到倍增,也就是说设f[i][j]记录第i个位置的数后面第2^j个递增极大值点所在的位置,那么我们用倍增判断[l,r]内有多少个递增极大值点的复杂度就变为logn,这样复杂度就够了,需要注意的一个点是我们在倍增过程中的退出条件是f[l][i]<=r,有可能从l出发一次跳的比r大,这种状态对应的是两种情况,一种是区间[l,r]是递增的,那么这种情况下本身就不需要考虑,但是还有一种情况我们不得不考虑,就是[l,r]中存在一个极大值点,从这个极大值点找到下一个大于当前极大值点的位置是大于等于r的,如果我们不考虑这种情况我们就会少算答案。所以那这种情况怎么办呢?我们f[i][0]记录的是i后面能够跳到的第一个递增极大值点,这是不包含第i个位置的,而刚才这种情况我们是需要考虑到i这个位置的,所以我们需要新开一个ne[i]表示i后面(包含i)的第一个极大值点出现的位置,这样就可以O(1)判断倍增后的区间[l,r)中是否还有极大值点了。

    细节见代码:

    1. #include<cstdio>
    2. #include<iostream>
    3. #include<algorithm>
    4. #include<cstring>
    5. #include<map>
    6. #include<queue>
    7. #include<vector>
    8. #include<cmath>
    9. using namespace std;
    10. const int N=1e5+10;
    11. int st[N],tt,a[N];
    12. int f[N][21];//f[i][j]记录第i个位置的数后面第2^j个递增极大值点所在的位置
    13. int mp[N],ne[N];//ne[i]记录第i个数及之后的数中出现的第一个极大值点的位置
    14. //注意:此处的极大值与数学中的极大值略有不同,此处的第i个位置为极大值点当且仅当a[i]>a[i+1],不要求a[i]>a[i-1]
    15. int main()
    16. {
    17. int T;
    18. cin>>T;
    19. while(T--)
    20. {
    21. int n,q;
    22. scanf("%d%d",&n,&q);
    23. tt=0;
    24. for(int i=1;i<=n;i++)
    25. {
    26. scanf("%d",&a[i]);
    27. mp[a[i]]=i;
    28. }
    29. for(int i=n;i>=1;i--)//单调栈求每个数后面第一个比他大的数的位置
    30. {
    31. while(tt&&a[i]>st[tt]) tt--;
    32. if(!tt) f[i][0]=n+1;
    33. else f[i][0]=mp[st[tt]];
    34. st[++tt]=a[i];
    35. }
    36. ne[n]=n+1;//后面没有极大值就默认极大值出现的位置是n+1
    37. for(int i=n-1;i>=1;i--)
    38. if(f[i][0]==i+1)
    39. {
    40. f[i][0]=f[i+1][0];
    41. ne[i]=ne[i+1];
    42. }
    43. else ne[i]=i;
    44. for(int i=n-1;i>=1;i--)//倍增预处理
    45. for(int j=1;j<=20;j++)
    46. f[i][j]=f[f[i][j-1]][j-1];
    47. while(q--)
    48. {
    49. int ans=0,l,r;
    50. scanf("%d%d",&l,&r);
    51. for(int i=20;i>=0;i--)
    52. {
    53. if(f[l][i]&&f[l][i]<=r)
    54. {
    55. l=f[l][i];
    56. ans+=1<<i;
    57. }
    58. }
    59. if(ne[l]<r) ans++;//如果区间[l,r-1]之间还有一个极大值,那么就还需要一次转移
    60. printf("%d\n",ans);
    61. }
    62. }
    63. return 0;
    64. }

  • 相关阅读:
    手机浏览器看视频加载太慢怎么办,这5招用了提速快
    2024年两会-区块链方向-新质生产力-先进制造业集群
    SOC TOP集成基础脚本范例
    Android系统10 RK3399 init进程启动(三十八) 属性Selinux实战编程
    【Web基础】Web应用体系结构 — 容器 + MVC设计模式
    C和指针 第13章 高级指针话题 13.2 高级声明
    青团平台全新上线,效果图渲染单张优惠低至2元封顶
    HTML分类面试题
    RabbitMQ和Minio实现头像存储
    系列三、Linux中安装Nginx
  • 原文地址:https://blog.csdn.net/AC__dream/article/details/126395569