題意:
你要按順序買 個物品,每個物品的花費分別爲 。初始時有一張價值爲 的代金券。
每次最多可以使用 元的代金券,此時如果消費x元,則能得到 元新的代金券。
問需要花費的最少代價。
做法:
感覺這題dp的做法還是挺好理解的吧。
表示前i天得到了j元的代金券所用的最少錢數。
記 表示 的前綴和,則第i天能用的代金券錢數就等於 。
轉移的時候直接枚舉花多少代金券即可,然後由於要輸出方案就記錄一個 數組保存最優方案。
然後這樣複雜度看起來有點大qwq。
考慮 i 和 k 的循環時間複雜度爲 , ,因此整個時間複雜度是 ,最壞情況計算次數 。由於時限 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;
}