P9519 题解
分析
由题可知,显然当 $k$ 的值逐渐增大时,每个员工产生的贡献也是逐渐增大的,对于这种具有单调性的条件,我们可以考虑运用二分答案进行求解。
首先确定二分范围:左边界显然是1,因为不可能不发工资或者让员工倒贴钱,即使某些专家依然在提倡付费上班。
对于右边界,当 $k$ 取 $\max a_i + n - 1$ 就可以保证满足每一个员工。
确定了二分边界,接下来需要思考的就是如何在 $O(n)$ 的时间内计算好每一个员工的贡献。
可以考虑用队列维护对当前位置有贡献的员工编号。
每当左移一个位置,产生的贡献值 $tmp \to tmp - |a|$,如果队列某个员工对后面的员工不再产生贡献了,将他(她)移出队列即可。
先顺序处理一遍,再逆序处理一遍即可,总的时间复杂度为 $O(n\log n)$。
具体细节请看代码……
代码
#include <iostream>
#include <algorithm>
#include <queue>
#define MAXN 1000005
#define MAXM 1000005
using namespace std; // 预处理
int n, m, l, r, mid, maxx, ans;
int a[MAXN];
bool b[MAXN]; // 记录当前位置是否可以产生贡献
long long f[MAXN], tmp; // f数组存储当前 k 值下每一个员工的贡献,tmp 记录各个对当前位置产生贡献的总和
queue <int> q; // 队列维护对当前位置产生贡献的员工编号
int read(){ // 快读
    int t = 1, x = 0;char ch = getchar();
    while(!isdigit(ch)){if(ch == '-')t = -1;ch = getchar();}
    while(isdigit(ch)){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * t;
}
bool check(int k){
    while(!q.empty())q.pop();tmp = 0;
    for(int i = 1 ; i <= n ; i ++)f[i] = 0; // 初始化
    for(int i = 1 ; i <= n ; i ++){
        tmp -= q.size(); // 更新当前位置贡献值
        if(!q.empty() && i - q.front() >= k)q.pop(); // 如果该员工对后面的员工不再产生贡献了,那么就将他(她)移出队列
        if(b[i] == true){tmp += k;q.push(i);} // 如果当前位置可以对其他位置产生贡献,那么将他(她)加入队列
        f[i] += tmp; // 储存当前位置贡献值
    }
    while(!q.empty())q.pop();tmp = 0; // 初始化
    for(int i = n ; i >= 1 ; i --){
        tmp -= q.size(); // 更新当前位置贡献值
        if(!q.empty() && q.front() - i >= k)q.pop(); // 如果该员工对后面的员工不再产生贡献了,那么就将他(她)移出队列
        if(b[i] == true){tmp += k;q.push(i);} // 如果当前位置可以对其他位置产生贡献,那么将他(她)加入队列
        f[i] += tmp; // 储存当前位置贡献值
        if(b[i] == true)f[i] -= k; // 需要注意的是,这时自身产生的贡献被计算了两次,所以需要减去 k
    }
    for(int i = 1 ; i <= n ; i ++) // 判断当前 k 值 是否符合题意
        if(f[i] < a[i])return false;
    return true;
}
int main(){
    n = read();m = read();
    for(int i = 1 ; i <= n ; i ++)a[i] = read();
    for(int i = 1 ; i <= m ; i ++){
        mid = read();b[mid] = true;
    }
    for(int i = 1 ; i <= n ; i ++)maxx = max(a[i], maxx);
    l = 1, r = maxx + n - 1; // 初始化二分左右边界
    while(l <= r){
        mid = l + r >> 1;
        if(check(mid) == true){
            ans = mid;r = mid - 1; // 如果当前 mid 值符合题意,那么更新 ans 值
        }else l = mid + 1;
    }
    cout << ans << endl;return 0; // 输出答案
} // 完结撒花~~
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号