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;
}
CodeForces - 616D是一个关于找到一个序列中最长的第k好子段的起始位置和结束位置的问题。给定一个长度为n的序列和一个整数k,需要找到一个子段,该子段中不超过k个不同的数字。题目要求输出这个序列最长的第k好子段的起始位置和终止位置。 解决这个问题的方法有两种。第一种方法是使用尺取算法,通过维护一个滑动窗口来记录\[l,r\]中不同数的个数。每次如果这个数小于k,就将r向右移动一位;如果已经大于k,则将l向右移动一位,直到个数不大于k。每次更新完r之后,判断r-l+1是否比已有答案更优来更新答案。这种方法的时间复杂度为O(n)。 第二种方法是使用枚举r和双指针的方法。通过维护一个最小的l,满足\[l,r\]最多只有k种数。使用一个map来判断数的种类。遍历序列,如果当前数字在map中不存在,则将种类数sum加一;如果sum大于k,则将l向右移动一位,直到sum不大于k。每次更新完r之后,判断i-l+1是否大于等于y-x+1来更新答案。这种方法的时间复杂度为O(n)。 以上是两种解决CodeForces - 616D问题的方法。具体的代码实现可以参考引用\[1\]和引用\[2\]中的代码。 #### 引用[.reference_title] - *1* [CodeForces 616 D. Longest k-Good Segment(尺取)](https://blog.csdn.net/V5ZSQ/article/details/50750827)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Codeforces616 D. Longest k-Good Segment(双指针+map)](https://blog.csdn.net/weixin_44178736/article/details/114328999)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值