• 51nod1885 区间和2 [二分+尺取]


    2区间和2



    color{red}{正解部分}

    题目要求的是 i=LRBisumlimits_{i=L}^R B_i, 转化为求 i=1RBii=1L1Bisumlimits_{i=1}^R B_i-sumlimits_{i=1}^{L-1} B_i,
    求前 KK 小所有区间的前缀和 .

    怎么求前 KK 小区间的和呢 ? 首先找出所有区间再去找第前KK个区间 是不可行的, 考虑二分答案,

    先二分出 midmid, 转化为 判断是否有KK个区间比midmid,
    考虑怎么枚举区间, 有哪些算法可以快速地求出 符合某些条件的区间的个数 ??

    尺取可以快速地求出符合条件的区间个数, 使用 尺取 统计 AA 数列中有多少子段是 midle mid 的, 设为 numnum,

    • num<Knum < K, 说明 midmid 小了 .
    • num=Knum = K, 理想情况, 表示正好找到了 .
    • num>Knum > K, 此时注意, 可能去掉一些权值等于 midmid 的区间就可以使得 num=midnum = mid 了, 否则说明 midmid 大了 .

    这里说明一下为什么可以尺取, 因为对每个左端点, 右端点往右移产生的新区间的和满足单调递增, 又因为有界限存在, 所以可以不重不漏地照顾到所有满足条件的区间.

    但若出现负数, 就不可以这么做了, 这道题 就是一个例子 .


    color{red}{实现部分}

    注意询问中的 L,RL,R 由于区间的数量是 N2N^2 级别的, 所以要开 long longlong long .

    #include<bits/stdc++.h>
    #define reg register
    typedef long long ll;
    
    const int maxn = 200005;
    
    int N;
    int Q;
    int A[maxn];
    int sum[maxn];
    
    int chk(int mid, ll K, ll &tmp){
            int t = 1, cnt_e = 0;
            ll res = 0, cur_sum = 0, cnt = 0;
            for(reg int i = 1; i <= N; i ++){
                    while(t <= N && sum[t]-sum[i-1] <= mid) cur_sum += sum[t ++];
                    cnt += t - i;
                    if(sum[t-1]-sum[i-1] == mid) cnt_e ++;
                    res += cur_sum - (1ll*t-i)*sum[i-1];
                    cur_sum -= sum[i];
            }
            tmp = res;
            if(cnt < K) return 1;		// mid 太小 
            else if(cnt-cnt_e < K){ tmp -= (cnt-K)*mid; return 1; }
            return 0;
    }
    
    ll Calc(ll x){
            int l = 1, r = sum[N];
            ll res = 0;
            while(l <= r){
                    int mid = l+r >> 1;
                    ll tmp = 0;
                    if(chk(mid, x, tmp)) res = tmp, l = mid + 1;
                    else r = mid - 1;
            }
            return res;
    }
    
    void Work(){ 
            scanf("%d%d", &N, &Q);
    		for(reg int i = 1; i <= N; i ++) scanf("%d", &A[i]);
    		for(reg int i = 1; i <= N; i ++) sum[i] = sum[i-1] + A[i];
            while(Q --){
                    ll L, R;
                    scanf("%lld%lld", &L, &R);
                    printf("%lld
    ", Calc(R) - Calc(L-1));
            }
    }
    
    int main(){
    		int T;
        	scanf("%d", &T);
        	while(T --) Work();
    		return 0;
    }
    
  • 相关阅读:
    电脑连不上网
    decompiler of java
    emmmmmmmmmmmmmmmmmm01
    WEB-INF
    tan?
    spring配置
    maven安装和使用前的几个点
    ※剑指offer系列29:两个链表的第一个公共结点
    剑指offer系列28:数组中的逆序对
    剑指offer系列27:第一个只出现一次的字符
  • 原文地址:https://www.cnblogs.com/zbr162/p/11822525.html
Copyright © 2020-2023  润新知