2300专项:D. Stressful Training(二分 充电)

原题: http://codeforces.com/problemset/problem/1132/D

题意: k-1分钟,n个点,初值为ai,每分钟消耗bi。你现在可以选择一个p大小的充电器,每分钟选择一个点充。问最小的p使得没有一个点在任意一分钟小于0。

解析:

二分答案值,对于答案p进行分析。

对于每个点,只有当它变成负数的时候再+p。而如果前面有几分钟没有加过,我可以让那分钟充这个点。形象一定,如果得出的增加时间为1、3、4、4、5,那么我可以让空出来的2来充4,所以只要满足 i j = 1 i c t [ j ] < = i \forall i\sum_{j=1}^ict[j]<=i 即可。

这个在什么时候判呢?因为充电次数最多为k-1,所以我可以每次充电时候查询当前时间即可。
最后再从1~k判一遍即可。

为什么最后还要再判一遍?例如第一个点用了2、2、3,发现没什么问题,第二点用了1,也没有问题。但是结果是错的。

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=(int)(a);i<=(int)(b);i++)
#define LL long long
const int maxn=2e5+5;
LL a[maxn],b[maxn];
LL tr[maxn];
int n,k;
void update(int p,int v){
    while(p<=k){
        tr[p]+=v;
        p+=p&-p;
    }
}
LL query(int p){
    LL res=0;
    while(p){
        res+=tr[p];
        p-=p&-p;
    }
    return res;
}
bool check(LL p){
    memset(tr,0,sizeof tr);
    rep(i,1,n){
        LL ct=1;
        LL now=a[i];
        while(1){
            LL sub=now/b[i];
            ct+=sub;
            now-=sub*b[i];
            if(ct>=k)break;

            now-=b[i];
            while(now<0){
                update(ct,1);
                if(query(ct)>ct)return 0;
                now+=p;
            }
            ct++;
        }
    }
    rep(i,1,k){
        if(query(i)>i)return 0;
    }
    return 1;
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin>>n>>k;
    rep(i,1,n)cin>>a[i];
    rep(i,1,n)cin>>b[i];
    if(k==1){
        cout<<0<<endl;return 0;
    }
    LL l=0,r=1e18;
    if(check(0))cout<<0<<endl;
    else if(!check(1e18))cout<<-1<<endl;
    else{
        while(r-l>1){
            LL mid=l+r>>1;
            if(check(mid))r=mid;
            else l=mid;
        }
        cout<<r<<endl;
    }
}

猜你喜欢

转载自blog.csdn.net/jk_chen_acmer/article/details/89402211