【Codeforces 730F】Ber Patio
贪心,动态规划。
题目大意:
你一开始有 $b$ 元的代金券。接下来有 $n$ 天,第 $i$ 天消费 $a_i$ 元。第 $i$ 天你可以使用至多 $\lfloor\frac {a_i}2\rfloor $ 元的代金券,剩下的部分需要支付现金。若你支付了 $x$ 元现金,则得到 $\lfloor\frac x {10}\rfloor$ 元代金券。
问你至少要付多少现金,并输出一种每天使用代金券数量的方案。
题解:
根据数据范围可知,总共得到的代金券的数量不超过 $10^4$。
令 $f_{i,j}$ 表示前 $i$ 天,总共得到 $j$ 元代金券,花费的最少现金。
那么状态 $i,j$ 剩下的代金券数量为 $j-\left(\sum_{k=1}^i a_k\right)+f_{i,j}$。
在不影响代金券总量的情况下,一张代金券早用和晚用是一样的,所以我们只需要记录最少现金即可。
然后枚举第 $i$ 天使用的现金个数进行转移即可。
直接做的复杂度是 $O(\left(\sum_{i=1}^n a_i\right)^2n)$。
这是个类似背包的转移,我们每次只转移 $\sum_{k=1}^i a_k$ 的状态,类似树上背包的,时间复杂度可优化至 $O(\left(\sum_{i=1}^n a_i\right)^2)$。常数大概 $\frac 1{20}$,可以通过。
记录状态的话,再开一个数组记录前驱状态即可。
开两个 $5000\times 10000$ 的 int 数组的空间可以接受。
Code:
#include<cstdio>
#include<cstring>
#include<algorithm>
const int N=5050;
int n,b,a[N],dp[N][N*2],sum,ans[N];
short pre[N][N*2];
int main(){
scanf("%d%d",&n,&b);
for(int i=1;i<=n;++i)scanf("%d",a+i);
memset(dp,-1,sizeof dp);
**dp=0;
int tot=0;
for(int i=1;i<=n;++i){
int*f=dp[i],*g=dp[i-1];
tot+=a[i]/10;
for(int j=0;j<=tot;++j)
if(-1!=g[j])
for(int k=a[i]+1>>1;k<=a[i]&&j+k/10<=tot;++k){
int ticket=b+j-(sum-g[j]);
if(a[i]-k>ticket)continue;
int nw=g[j]+k,&F=f[j+k/10];
if(!~F||F>nw)F=nw,pre[i][j+k/10]=j;
}
sum+=a[i];
}
int mx=1919810,p=0;
for(int i=0;i<=10000;++i)if(mx>dp[n][i]&&~dp[n][i])
mx=dp[n][i],p=i;
for(int i=n;i;--i){
int cost=dp[i][p]-dp[i-1][pre[i][p]];
ans[i]=a[i]-cost;
p=pre[i][p];
}
printf("%d\n",mx);
for(int i=1;i<=n;++i)
printf("%d ",ans[i]);
return 0;
}