codeforces730F - Ber Patio

题面在这里

题意:

你要按顺序买 n n 个物品,每个物品的花费分别为a1...an。初始时有一张价值为 b b 的代金券。

每次最多可以使用min(ai2,b)元的代金券,此时如果消费x元,则能得到 x/10 ⌊ x / 10 ⌋ 元新的代金券。

问需要花费的最少代价。

n<=5000,ai<=1000,ai<=105,b<=105 n <= 5000 , a i <= 1000 , ∑ a i <= 10 5 , b <= 10 5

做法:

感觉这题dp的做法还是挺好理解的吧。

f[i][j] f [ i ] [ j ] 表示前i天得到了j元的代金券所用的最少钱数。

sum[i] s u m [ i ] 表示 a[i] a [ i ] 的前缀和,则第i天能用的代金券钱数就等于 b(sum[i]f[i][j])+j b − ( s u m [ i ] − f [ i ] [ j ] ) + j

转移的时候直接枚举花多少代金券即可,然后由于要输出方案就记录一个 pre p r e 数组保存最优方案。

然后这样复杂度看起来有点大qwq。

考虑 i 和 k 的循环时间复杂度为 ai25×104 ∑ a i 2 ≤ 5 × 10 4 jai10104 j ≤ ∑ a i 10 ≤ 10 4 ,因此整个时间复杂度是 O((ai)220) O ( ( ∑ a i ) 2 20 ) ,最坏情况计算次数 5×108 ≤ 5 × 10 8 。由于时限 3s ,因此是可以过的(事实上最慢的点跑了还不到 1shhhh)。

——摘自这里

然而我发现我的程序总共只跑了200+ms。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<iostream>
#include<cmath>
#include<cstdlib>
#include<cctype>
using namespace std;
typedef long long ll;

inline ll read() {
    char ch = getchar(); ll x = 0; int op = 1;
    for(; !isdigit(ch); ch = getchar()) if(ch == '-') op = -1;
    for(; isdigit(ch); ch = getchar()) x = x*10+ch-'0';
    return x*op;
}
inline void write(ll a) {
    if(a < 0) putchar('-'), a = -a;
    if(a >= 10) write(a/10); putchar('0'+a%10);
}

const int N = 5005, M = 10005;
const int inf = 1e9;
int n, m, now;
int a[N], sum[N], f[M], g[M], b[N], pre[N][M];

int main() {
    n = read(); m = read();
    for(int i = 1; i <= n; i ++) sum[i] = sum[i-1]+(a[i] = read());
    //f[i][j]表示前i天得到了j元的代金券所用的最少钱数,加滚动数组
    memset(f, 0x3f, sizeof f);
    f[0] = 0;
    for(int i = 1; i <= n; i ++) {
        now = min(now+a[i]/10, M);
        for(int j = 0; j <= now; j ++) { g[j] = f[j]; f[j] = inf; }//滚动 
        for(int j = 0; j <= now; j ++) if(g[j] <= sum[i-1]) {//枚举1~i-1天得到了多少代金券 
            int t = m-(sum[i-1]-g[j])+j;//sum[i-1]-g[j]就是1~i-1天用的代金券钱数 
            //t就是目前剩下的代金券钱数 
            for(int k = 0; k <= a[i]/2 && k <= t; k ++) {//枚举第i天用多少代金券 
                int r = (a[i]-k)/10;//能获得这么多新的代金券
                if(g[j]+a[i]-k < f[j+r]) {
                    f[j+r] = g[j]+a[i]-k;
                    pre[i][j+r] = k;//记录最优方案
                }
            }
        }
    }
    int ans = 0;
    for(int i = 1; i <= now; i ++) if(f[i] < f[ans]) ans = i;
    write(f[ans]); puts("");
    for(int i = n; i >= 1; i --) {
        b[i] = pre[i][ans];
        ans -= (a[i]-pre[i][ans])/10;
    }
    for(int i = 1; i <= n; i ++) write(b[i]), putchar(' ');
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值