攀爬 [二分, 思维]

攀爬



color{red}{正解部分}

若存在一个合法的攀爬序列, 则其形式一定是 ak+i=1m(aibi)La_{k} + sumlimits_{i=1}^m (a_i - b_i) geq L,
于是考虑枚举 aka_k, 设 di=aibid_i = a_i-b_i, 按 dd 从大到小 排序, 然后 O(N)O(N) 解出 爬出井 的最早时间, 更新答案, 总时间复杂度 O(N2)O(N^2) .

考虑怎么优化, 记 sumi=j=1idjsum_i = sumlimits_{j=1}^i d_j, 设最早被淹死的时间为 dayday, day=min(i  sumij=1icj)day = min(i | sum_i le sumlimits_{j=1}^i c_j),
只需要在 sumi,i[1,day]sum_i, i ∈ [1, day]二分查找 LakL - a_k 即可得到 关于 aka_k 的答案,

但是随着 aka_k 的变化, sumisum_i 同样也在变化, 我们要使得这种变化更加 “平滑”, 更加易于控制一点,

于是我们将 aka_k 从右往左 枚举, 考虑取出 aka_k 会对哪些 sumisum_i 造成影响,
会对 sump,p[k,n]sum_p, p ∈ [k, n] 造成影响, 这种变化会使得 sump=sump+di+1dksum^{'}_p = sum_p + d_{i+1} - d_k,

由于 dkd_k 从右向左 枚举, 且 {di}{d_i} 是 递减的, 所以 dkd_k 是不断增大的, 进而 sumpsum^{'}_p 是不断减少的,
所以 dayday 只有可能不断向左移动, 而向左移动最多移动 NN 步, 所以按上述方法暴力移动 dayday, 总复杂度是 O(NlogN)O(N log N) 的 .


但是这里要提到的是, 当出现 di<0d_i < 0 的情况时, sumi{sum_i} 并非单调,
为了应对这种情况, 需要找出一个中准点 mdmd, 使得 [1,md][1, md] 是单调递增的, 在 [1,min(day,md)][1, min(day, md)]二分查找 即可 .


color{red}{实现部分}

  • 注意一步就能跳到井口的情况需要特判 .
#include<bits/stdc++.h>
#define reg register
typedef long long ll;

int read(){
        char c;
        int s = 0, flag = 1;
        while((c=getchar()) && !isdigit(c))
                if(c == '-'){ flag = -1, c = getchar(); break ; }
        while(isdigit(c)) s = s*10 + c-'0', c = getchar();
        return s * flag;
}

const int maxn = 100005;

int N;
int L;

ll C[maxn];
ll sum[maxn];

struct Pill{ int a, b, d; } A[maxn];

bool cmp(Pill x, Pill y){ return x.d > y.d; }

int lower_bound(int l, int r, const int &aim, const int &k){
        int res = -1;
        while(l <= r){
                int mid = l+r >> 1;
                ll tmp = sum[mid] + ((mid>=k)?(-A[k].d+A[mid+1].d):0);
                (tmp >= aim) ? r = mid - 1, res = mid : l = mid + 1;
        }
        return res;
}

int main(){
        N = read(), L = read();
        for(reg int i = 1; i <= N; i ++) A[i].a = read(), A[i].b = read(), A[i].d = A[i].a - A[i].b;
        std::sort(A+1, A+N+1, cmp);
        for(reg int i = 1; i <= N; i ++) C[i] = C[i-1] + read();
        int day = N;
        for(reg int i = 1; i <= N; i ++){
                sum[i] = sum[i-1] + A[i].d;
                if(sum[i] <= C[i]) day = std::min(day, i);
                if(sum[i] < sum[i-1] && (i-1)) day = std::min(day, i);
        }
        int Ans = -1;
        for(reg int k = N; k >= 1; k --){
                if(day >= k){ 
                        ll d = sum[day] - A[k].d + A[day+1].d;
                        while(day >= k && d <= C[day]) day --, d = sum[day]-A[k].d+A[day+1].d;
                }
                int pos = lower_bound(1, day, L-A[k].a, k);
                if(pos != -1){ if(Ans == -1) Ans = pos; else Ans = std::min(Ans, pos); }
        }
        if(Ans != -1) Ans ++;
        if(Ans == 2) Ans --;
        printf("%d
", Ans);
        return 0;
}
原文地址:https://www.cnblogs.com/zbr162/p/11822449.html