摆渡车(二维dp/前缀和)

题目

[NOIP2018 普及组] 摆渡车

题目描述

n n n 名同学要乘坐摆渡车从人大附中前往人民大学,第 i i i 位同学在第 t i t_i ti 分钟去 等车。只有一辆摆渡车在工作,但摆渡车容量可以视为无限大。摆渡车从人大附中出发、 把车上的同学送到人民大学、再回到人大附中(去接其他同学),这样往返一趟总共花费 m m m 分钟(同学上下车时间忽略不计)。摆渡车要将所有同学都送到人民大学。

凯凯很好奇,如果他能任意安排摆渡车出发的时间,那么这些同学的等车时间之和最小为多少呢?

注意:摆渡车回到人大附中后可以即刻出发。

输入格式

第一行包含两个正整数 n , m n, m n,m,以一个空格分开,分别代表等车人数和摆渡车往返一趟的时间。
第二行包含 n n n 个正整数,相邻两数之间以一个空格分隔,第 i i i 个非负整数 t i t_i ti 代表第 i i i 个同学到达车站的时刻。

输出格式

输出一行,一个整数,表示所有同学等车时间之和的最小值(单位:分钟)。

样例 #1

样例输入 #1

5 1 
3 4 4 3 5

样例输出 #1

0

样例 #2

样例输入 #2

5 5 
11 13 1 5 5

样例输出 #2

4

提示

【输入输出样例 1 说明】

同学 1 1 1 和同学 4 4 4 在第 3 3 3 分钟开始等车,等待 0 0 0 分钟,在第 3 3 3 分钟乘坐摆渡车出发。摆渡车在第 4 4 4 分钟回到人大附中。
同学 2 2 2 和同学 3 3 3 在第 4 4 4 分钟开始等车,等待 0 0 0 分钟,在第 4 4 4 分钟乘坐摆渡车 出发。摆渡车在第 5 5 5 分钟回到人大附中。
同学 5 5 5 在第 5 5 5 分钟开始等车,等待 0 0 0 分钟,在第 5 5 5 分钟乘坐摆渡车出发。自此 所有同学都被送到人民大学。总等待时间为 0 0 0

【输入输出样例 2 说明】

同学 3 3 3 在第 1 1 1 分钟开始等车,等待 0 0 0 分钟,在第 1 1 1 分钟乘坐摆渡车出发。摆渡 车在第 6 6 6 分钟回到人大附中。
同学 4 4 4 和同学 5 5 5 在第 5 5 5 分钟开始等车,等待 1 1 1 分钟,在第 6 6 6 分钟乘坐摆渡车 出发。摆渡车在第 11 11 11 分钟回到人大附中。
同学 1 1 1 在第 11 11 11 分钟开始等车,等待 2 2 2 分钟;同学 2 2 2 在第 13 13 13 分钟开始等车, 等待 0 0 0 分钟。他/她们在第 13 13 13 分钟乘坐摆渡车出发。自此所有同学都被送到人民大学。 总等待时间为 4 4 4
可以证明,没有总等待时间小于 4 4 4 的方案。

【数据规模与约定】
对于 10 % 10\% 10% 的数据, n ≤ 10 , m = 1 , 0 ≤ t i ≤ 100 n ≤ 10, m = 1, 0 ≤ t_i ≤ 100 n10,m=1,0ti100
对于 30 % 30\% 30% 的数据, n ≤ 20 , m ≤ 2 , 0 ≤ t i ≤ 100 n ≤ 20, m ≤ 2, 0 ≤ t_i ≤ 100 n20,m2,0ti100
对于 50 % 50\% 50% 的数据, n ≤ 500 , m ≤ 100 , 0 ≤ t i ≤ 1 0 4 n ≤ 500, m ≤ 100, 0 ≤ t_i ≤ 10^4 n500,m100,0ti104
另有 20 % 20\% 20% 的数据, n ≤ 500 , m ≤ 10 , 0 ≤ t i ≤ 4 × 1 0 6 n ≤ 500, m ≤ 10, 0 ≤ t_i ≤ 4 \times 10^6 n500,m10,0ti4×106
对于 100 % 100\% 100% 的数据, n ≤ 500 , m ≤ 100 , 0 ≤ t i ≤ 4 × 1 0 6 n ≤ 500, m ≤ 100, 0 ≤ t_i ≤ 4 \times 10^6 n500,m100,0ti4×106

思路

先研究每个学生需要等待的最长的时间。

  • 假设在t时刻,车刚出发,而学生在t+1时刻到达,此时他需要等待车回来。等车时间T1<m
  • 车回来后,此时为t+m,车最多等待m秒。如果更长,它完全可以先开一次,再回来。车从回来,到重新出发时间T2<m
    综上,学生需要等待的最多时间为T1+T2<2m秒。

其次,应该按照学生的抵达顺序来先后处理,排个序总没错。

sort(a, a + n);

每个学生的抵达时间已知,以及他们对应的最长等待时间已知。再根据题目数据范围。
很容易想到二维dp
定义dp[i][j]表示,第i个学生,在等待j时刻后重新上车。此时,运送前i个学生,需要花费的最小等待时间。

暴力dp

来个暴力dp
用dp[i-1][k]来更新dp[i][j],总共就两种情况:

  • 刚好可以蹭到上一班的车。a[i-1] + k == a[i] + j
  • 等待了j时刻后,上一班的车已经重新回来了。a[i-1] + k + m <= a[i] + j

时间复杂度O(4nmm)。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define pcc pair<char, char>
#define inf 0x3f3f3f3f
const int maxn = 510;
const int maxm = 210;

int n, m, m2;
int a[maxn]; 
int dp[maxn][maxm];
void solve() { 
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; ++i) {
    	scanf("%d", &a[i]);
	}
	sort(a, a + n);
	m2 = m * 2; 
	for (int j = 0; j < m2; ++j) {
		dp[0][j] = j;
	}
	for (int i = 1; i < n; ++i) {
		int tmp = a[i] - a[i-1];
		for (int j = 0; j < m2; ++j) {
			dp[i][j] = inf;
			for (int k = 0; k < m2; ++k) {
				// case1: a[i-1] + k == a[i] + j.
				// case2: a[i-1] + k + m <= a[i] + j
				if (k == tmp + j || k + m <= tmp + j) {
					if (dp[i-1][k] != inf) dp[i][j] = min(dp[i][j], dp[i-1][k] + j);
				}
			}
		}
	}

	int res = inf;
	for (int j = 0; j < m2; ++j) {
		res = min(dp[n-1][j], res);
	}
	printf("%d\n", res);
}

int main() {
    int t;
//    scanf("%d", &t);
    t = 1;
    while (t--) {
    	solve();
	}
}
/*

*/

前缀和优化

根据上述dp转移方程,我们容易发现可以前缀和优化下。
优化后时间复杂度O(2nm)

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define pcc pair<char, char>
#define inf 0x3f3f3f3f
const int maxn = 510;
const int maxm = 210;

int n, m, m2;
int a[maxn]; 
int dp[maxn][maxm];
int pre[maxm], cur[maxm];
void solve() { 
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; ++i) {
    	scanf("%d", &a[i]);
	}
	sort(a, a + n);
	m2 = m * 2; 
	for (int j = 0; j < m2; ++j) {
		dp[0][j] = j;
		pre[j] = 0;
	}
	for (int i = 1; i < n; ++i) {
		int tmp = a[i] - a[i-1];
		for (int j = 0; j < m2; ++j) {
			dp[i][j] = inf;
			if (tmp + j - m >= 0 && pre[min(tmp+j-m, m2-1)] != inf) {
				dp[i][j] = min(dp[i][j], pre[min(tmp+j-m, m2-1)] + j);
			}
			if (tmp + j >= 0 && tmp + j < m2 && dp[i-1][tmp+j] != inf) {
				dp[i][j] = min(dp[i][j], dp[i-1][tmp+j] + j);
			}
			if (j) {
				cur[j] = min(cur[j-1], dp[i][j]);
			} else {
				cur[j] = dp[i][j];
			}
			/*
			for (int k = 0; k < m2; ++k) {
				// case1: a[i-1] + k == a[i] + j.
				// case2: a[i-1] + k + m <= a[i] + j
				if (k == tmp + j || k + m <= tmp + j) {
					if (dp[i-1][k] != inf) dp[i][j] = min(dp[i][j], dp[i-1][k] + j);
				}
			}*/
		}
		for (int j = 0; j < m2; ++j) {
			pre[j] = cur[j];
		}
	}

	int res = inf;
	for (int j = 0; j < m2; ++j) {
		res = min(dp[n-1][j], res);
	}
	printf("%d\n", res);
}

int main() {
    int t;
//    scanf("%d", &t);
    t = 1;
    while (t--) {
    	solve();
	}
}
/*

*/

瞄了眼洛谷上别人的dp题解,好像整得挺复杂的,还是自己的简洁233(自恋ing)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值