codeforces 727F. Polycarp's problems

8 篇文章 0 订阅
6 篇文章 0 订阅

题目链接http://codeforces.com/contest/727/problem/F
题目大意:有n个问题,每个问题有一个价值ai,一开始的心情值为q,每当读到一个问题时,心情值将会加上该问题的价值。问题只能按顺序读。有m个询问,求当q=bi时,至少要删去多少个问题才能使得在任何时候心情值都>=0。
数据范围:1 ≤ n ≤ 750, 1 ≤ m ≤ 200 000, - 10^9 ≤ ai ≤ 10^9, 0 ≤ bi ≤ 10^15

题解:显然我们不能等到询问的时候再处理,这样时间复杂度太大承受不下。所以我们可以先预处理出ans[i]表示如果要留下 i 个问题那么一开始的心情值至少要为多少,显然ans是非递减的序列,我们可以在询问的时候二分即可找出答案。处理出ans有两种方法:

1.二分+贪心
虽然复杂度有点危险但是因为本蒟蒻的贪心太差于是还是写了下来(反正cf评测机快hhhh)。
首先我们可以知道,如果ans[i]=x时满足条件,那么ans[i]=x+1时也是满足条件的(废话),于是我们还是可以二分。对于 i ,我们二分出mid,判断当q=mid时,能否留下 i 个问题。这里的判定我们可以用贪心。一开始心情值为mid,每当遇到一个新的问题 j 时,把心情值加上a[ j ],然后把a[j]存进一个小根堆里,如果当前的心情值小于0,就把心情值减去堆里最小的数(这必然是个负数且小于等于a[ j ]),然后把这个数移出堆。如果最后被移出堆的数<=n-i 就说明这个mid符合条件。询问的时候在ans上查找就可以了。
时间复杂度O(n^2*logn*log10^15+m*logn)

2.dp
一般想到dp方程都会设f[i][j]表示前 i 个问题留下 j 个问题,但是这样我们需要同时维护过程中的心情值和读完 j 个问题后的心情值以便之后的计算。所以我们换一种方法,令f[i][j]表示从第i~n个问题中留下 j 个问题所需要的最小心情值,这样我们只需要知道过程中的心情值即可,因为最后的心情值不会用到。于是得到转移方程f[i][j]=min(f[i+1][j],max(0,f[i+1][j-1]-a[i])。最后在f[1]上查找答案即可。
时间复杂度:O(n^2+m*logn)

代码如下:

1.二分+贪心

#include <algorithm>
#include <cstdio>
#include <vector>
#include <queue>
using namespace std;
int a[800],n,m;
long long ans[800];
bool check(long long mid,int x){
    priority_queue<int,vector<int>,greater<int> > q;
    long long sum=mid;
    for (int i=1;i<=n;i++){
        sum=sum+a[i];q.push(a[i]);
        if (sum<0){
            sum=sum-q.top();
            q.pop();
            if (--x<0) return 0;
        }
    }
    return 1;
}
long long calc(int x){
    long long l=0,r=1ll<<50,mid,ans;
    for (;l<=r;){
        mid=(l+r)>>1;
        if (check(mid,x)) ans=mid,r=mid-1;
            else l=mid+1;
    }
    return ans;
}
int main(){
    scanf("%d%d\n",&n,&m);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    for (int i=0;i<=n;i++) ans[n-i]=calc(i);
    for (int i=1;i<=m;i++){
        long long x;
        scanf("%I64d\n",&x);
        printf("%d\n",n-(upper_bound(ans,ans+n+1,x)-ans)+1);
    }
}

2.dp

#include <algorithm>
#include <cstring>
#include <cstdio>
int a[800],n,m;
long long f[800][800];
int main(){
    scanf("%d%d\n",&n,&m);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    memset(f,127,sizeof f);
    f[n+1][0]=0;
    for (int i=n;i;i--)
        for (int j=0;j<=n-i+1;j++){
            f[i][j]=f[i+1][j];
            if (j) f[i][j]=std::min(f[i][j],std::max(f[i+1][j-1]-a[i],0ll));
        }
    for (int i=1;i<=m;i++){
        long long x;
        scanf("%I64d\n",&x);
        printf("%d\n",n-(std::upper_bound(f[1],f[1]+n+1,x)-f[1]-1));
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值