Difference (第k大(二分)+双指针+ST表)+(很多小细节!!!!!) (MINIEYE杯十六届)

题目:D-Difference_MINIEYE杯第十六届华中科技大学程序设计邀请赛 (nowcoder.com)

大意: 求第k大的 函数值 (区间内的最大差值)*(区间长度)

思路:

  • 首先遇到第k大的什么值,就用二分答案,看看有几个比这个答案大的数
  • 另外可以看到, 区间 函数值(L,R) 是单调的, VAL(L,R)> VAL(L-1,R)||VAL(L,R-1),因为就算差值不变,区间长度也会减小,
  • 既然这个区间满足单调性,就可以利用双指针,来找有哪些区间比这个值大或者小, 
  • 这里 我选择的小 比较好理解, 那么利用减法原则,第k大可以转化为第K小 (所有值的数量-k+1;
  • 双指针:找有哪些数量的值比他小, 然后这个数量+1,就是第几大.
  • 找到所有比 a 小的 区间 (以L端点为极限值的), 就是 L-1,那么这个区间就大于 a 了, 因此 R到L 内的都是小的, 那么 L--R,L+1--R,L+2--R的子区间都是比他小哒
  • 而且我们是 从小到大枚举 R,所以 L到R-1这个区间已经被计算了,不用担心. zh
  • 何时移动 L呢? 就是这个区间已经大于等于a了, 就要移动了,L++,L也是从小到大枚举,而且这是区间单调,一定可以保证正确性和完整性
  • 二分判断的时候,要注意找到的值一定是这个数列里面有的,因此一定要搞到边缘值, 比如 数列 1 ,3  || 2 是第二大,3也是第二大,4,就是第3大了,3就是边缘值.
#include <bits/stdc++.h>
using namespace std;
#define  ri  int 
#define M 500007

long long m;
int n;
int  p[M];
int mx[M][19];
int mi[M][19];
int lg2[M];
void ST()
{
     for(int i = 2; i <= n; i++){
        lg2[i] = lg2[i >> 1] + 1;
    }
   for(ri i=1;i<=n;i++) mi[i][0]=p[i],mx[i][0]=p[i];
   for(ri i=1;i<=lg2[n];i++)
   {
        for(ri j=1;j+(1<<i)-1<=n;j++)
        {
            mi[j][i]=min(mi[j][i-1],mi[j+(1<<(i-1))][i-1]);
            mx[j][i]=max(mx[j][i-1],mx[j+(1<<(i-1))][i-1]);
     }
   }
}
long long qu(int a,int b)
{
    int k=lg2[b-a+1];
    long long mmx=max(mx[a][k],mx[b-(1<<k)+1][k]);
    long long mmi=min(mi[a][k],mi[b-(1<<k)+1][k]);
    return (mmx-mmi)*(b-a+1);
  
}
long long ck(long long  a) 
{
    long long ans=0;

    for(int i=1,j=1;j<=n;j++)
    {
        while(i<=j&&qu(i,j)>=a)    
        {
            i++;
        }
        ans+=(j-i+1);
    }
    ans++;
    return ans<=m;
}
int main(){
   
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cin>>n>>m;
    m=1ll*n*(n+1)/2-m+1;
    for(ri i=1;i<=n;i++)
    {
        cin>>p[i];
    }
    ST();
    long long l=0,r=qu(1,n)*n;
    long long ans=0;

    while(l<=r)
    {
        long long mid=(l+r)>>1;
        if(ck(mid))
        {
            ans=mid;
            l=mid+1;
        }
        else r=mid-1;
    }
    cout<<ans;
    return 0;
    
}
View Code

双指针模板: 

  • 遇到要求出说有的区间满足情况个数
  • 且区间时单调的
  • 因此只需要求出极限的区间范围,内部的区间,直接(r-l+1);
  • 注意下面这个是 i 到 j 里面是合理外面就不合理了, 越往外面扩展,就越可能不合理
  • 然后 j每次加一, 这个贡献就要加上当前的区间长度. 在处理的那个地方
    for(int i=1,j=1;j<=n;j++)
    {                          
        while(i<=j&&ck(i,j))    // 弄走不想要的情况
        {                      // 一定要特别主要 i==j时有些题目情况的特判
            i++;
        }
        进行处理
    }
    
View Code

 

好了,接下来,细节上的重头戏,耽搁了我4-5h找这些错

  • 首先还是要主要数据范围的大小int还longlong, (m是longlong,没太注意)
  • 记住!!! 大数据或者二维数组,的那种 一定要看 是int 还是 long long, 500005 20,用long long 就 G 
  • 所以在用long long 时 一定要关注这个范围大小,特别时二维数组本题就是 ST表上 后面跟了一个 20就每太管 
  • int 10^7 顶天了,可以方向用
  • 当然在有long long的题时,一定要看每一个步骤有没有超过int,超了就 1ll
  • 不过对于单个元素,用long long 没有问题,就不用每一步去看他超int每,对于二维数组一定不能图方便
  • 其次调用系统函数log2 的时间复杂度时 log的 ,不是O1,因此用ST表一定要自己写一个log2数组
  • 二分答案时一定要注意 L是不是小于0的,等于0的,!!!! 很重要!!!!
  • 用cin.tie(0)时,一定不用用printf, 自己写习惯了,用cout,写cin时,他的那个啥 关闭同步流一定要记得写
  • 写完intmain,就写,养成习惯

 

posted @ 2022-06-08 21:04  VxiaohuanV  阅读(63)  评论(0编辑  收藏  举报