ICPC 2020沈阳站 - H. The Boomsday Project(dp,双指针优化)

26 篇文章 5 订阅

https://codeforces.com/gym/103202/problem/H

题意
平常每次租用共享单车花费 r r r 元。
给定 n 种折扣卡,每张折扣卡 c i c_i ci 元,能够在包括购买当天的 d i d_i di 天内免费骑车 k i k_i ki 次。
现在有 m 条使用记录,每条记录给出 p i p_i pi q i q_i qi,表示将会在第 p i p_i pi 天租用单车 q i q_i qi 次。
问,如何购买折扣卡能使得总花费最小,输出最小花费。

1 ≤ n ≤ 500 , 1 ≤ m ≤ 1 0 5 , 1 ≤ r ≤ 1 0 9 1\leq n \leq 500, 1 \leq m \leq 10^5, 1 \leq r \leq 10^9 1n500,1m105,1r109
1 ≤ d i , k i , c i ≤ 1 0 9 1 \leq d_i, k_i, c_i \leq 10^9 1di,ki,ci109
0 ≤ p i ≤ 1 0 9 , 0 ≤ q i ≤ 3 × 1 0 5 , ∑ i = 1 m q i ≤ 3 × 1 0 5 0 \leq p_i \leq 10^9, 0 \leq q_i \leq 3 \times 10^5, \sum_{i=1}^m q_i \leq 3 \times 10^5 0pi109,0qi3×105,i=1mqi3×105

思路
注意到 q_i 的数据范围 ∑ i = 1 m q i ≤ 3 × 1 0 5 \sum_{i=1}^m q_i \leq 3 \times 10^5 i=1mqi3×105,可以把每天的 q i q_i qi 次记录铺开,所有的使用记录构成一个数列,表示都在第几天用了,例如:2 2 3 3 3 4 5 5,这样就转化成了 p i ∗ q i p_i*q_i piqi 条记录。
转化之后就和今年蓝桥杯的一道 dp 题(费用报销)类似。

定义状态 f[i] 表示,前 i 条记录的最小花费。

对于当前这一天,遍历所有种类的卡片,从前面找条记录 x 使用当前卡片,能够使得从第 x 条记录开始到当前这条记录都不用花费,那么状态就可以由前 x-1 条记录的花费加上卡片的花费来转移。
因为状态定义的是前 i 条记录的最小花费,那么越靠前花费越小,所以每次找到最靠前的满足条件的位置来转移
因为每种卡片免费的限制条件是使用次数和天数,而此时数组是按照时间排序的,所以对于每种卡片而言,随着遍历记录的后移,最靠前的满足条件的位置也单调往后走,所以可以用一个指针来指,每次遍历一条记录就往后走到转移位置,就省去了遍历转移位置的这一重遍历。

(一开始想在当前记录使用卡片,但是这样就会对后面产生贡献,当前这条记录的状态没办法转移,那么不妨在前面的某条记录使用,能对当前这条记录产生贡献,直接从其前一条记录的状态+卡片花费来转移就行了)

Code

#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define int long long

const int N = 300010, mod = 1e9+7;
int T, n, m;
int r;
struct node{
	int day, cnt, price;
}a[N];
int b[N];
int pos[N];
int f[N];

signed main(){
	Ios;
	cin >> n >> m >> r; 
	
	for(int i=1;i<=n;i++) cin >> a[i].day >> a[i].cnt >> a[i].price;
	
	int idx = 0;
	while(m--)
	{
		int x, y; cin >> x >> y;
		for(int i=1;i<=y;i++) b[++idx] = x;
	}
	sort(b+1, b+idx+1);
	
	for(int i=1;i<=idx;i++) f[i] = 1e18;
	for(int i=1;i<=n;i++) pos[i] = 1;
	
	for(int i=1;i<=idx;i++)
	{
		for(int j=1;j<=n;j++)
		{
			while(pos[j] < i && (b[i] - b[pos[j]] + 1 > a[j].day || i - pos[j] + 1 > a[j].cnt))
				pos[j] ++; //找到第一个满足的位置就停下
			
			f[i] = min(f[i], f[pos[j]-1] + a[j].price); //从其前一位置来转移
		}
	}
	cout << f[idx];
	
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值