H. The Boomsday Project (dp)

题目

The 2020 ICPC Asia Shenyang Regional Programming Contest
H. The Boomsday Project

大意:有一个人总是租单车。每次租车要 r 元。现在已知他将来m天的租车情况(每天要租p[i]辆车)。有 n 种优惠卡,第 i 种卡有个期限 d[i] 天(自买卡那天算起到第 d 天结束),可以免去这些天内的 k[i] 次租车钱,买卡要花 c[i] 元,一旦买了一张新卡,那之前的卡全部立即作废。问最少要花多少钱。

n<=500
sum ( p ) <=300000

分析

发现总租车数比较少,就把每次租车独立出来,按顺序进行dp。

先定义一下变量的意义:
cnt 代表着总的租车次数
a[i] 代表第 i 次租车所在的天。
f[i] 代表完成前 i 次租车所需的最少钱数。
ptr[i] 这个后面来解释

先是考虑最朴素的dp:
看伪代码以及里面的注释来理解:
先解释一下里面的 x 的意思:代表着在第 x 次租车结束之后办第 j 种卡,刚好可以免费到第 i 次租车。(若在第 (x-1) 次就不行了),具体的求法好说。

for i: [1 ~ cnt] {	//顺序枚举第 i 次租车
	f[i]= f[i-1]+r;	//先是默认情况,啥优惠都不用
	for j: [1 ~ n]		//枚举使用第 j 种优惠
		for t: [x ~ (i-1)]		//枚举那些使用该种优惠可以影响到第 i 次的那些租车事件
			f[i]= min(f[i], f[t]+c[j])		//看看在第 t 次租车结束后,买一张卡 j ,能否得到更优的效果。(即便有浪费免费次数也不管,反正更便宜了)
}

这样时间复杂度至少是 cnt * sigma(k,d) * (算 x 的时间),好像不太行

然后考虑优化:
注意到第三层循环,实际上是求 f[] 的区间最小值,又注意到 f[] 是递增的,所以那层循环可以直接去掉,最小值肯定是 f[x]。

for i: [1 ~ cnt] {	//顺序枚举第 i 次租车
	f[i]= f[i-1]+r;
	for j: [1 ~ n]		//枚举使用第 j 种优惠
		f[i]=min(f[i], f[x])
}

这样循环就变成两层了,应该行了,接下来就是 x 的求法了。
会发现,随着 i 的增加,对于每个 j 来说, x 也是递增的。那么对每个 j 弄个指针标记记录,一直往右移就行了。这样时间复杂度就单独出来变成了 n * cnt。这也就是前面的 pre[] 数组了。
具体实现看下面的代码。

那这样总的时间复杂度就是 O(cnt * n)。

代码

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;


int n,m;
long long r;
int d[505],k[505];
long long c[505];
int a[300005];
long long f[300005];
int cnt;
int ptr[505];

void solve()
{
	scanf("%d%d%lld",&n,&m,&r);
	for (int i=1; i<=n; i++) scanf("%d%d%lld",&d[i],&k[i],&c[i]);
	for (int i=1,p,q; i<=m; i++) {
		scanf("%d%d",&p,&q);
		for (int i=1; i<=q; i++) a[++cnt]=p;
	}
	
	sort(a+1,a+cnt+1);	//输入不是按天递增的,所以要排序 
	
	for (int i=1; i<=cnt; i++) {
		f[i]=f[i-1]+r;
		for (int j=1,x; j<=n; j++) {
			while (a[i]-a[ptr[j]+1]>=d[j] || i-ptr[j]>k[j]) ptr[j]++;
			f[i]=min(f[i], f[ptr[j]]+c[j]);
		}
	}
	
	printf("%lld\n",f[cnt]);
}



int main()
{
	solve();
	return 0;
}
  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值