P5017/Noip2018-PJT3 摆渡车 题解

update 2021/1/19:

  1. 原题解部分 Markdown 和 Latex 炸了,现在已经修复。
  2. 对题解当中一些表述不明的句子做了进一步的阐述。
  3. 更换了码风。

首先,看到这道题在普及组 T3 的位置,我们知道只能是 搜索/图论/DP 三者之一。

再结合数据范围我们就可以 根据个人经验 分析出这道题是 DP 题。

但或许是我太弱了,没有想出 DP 怎么写,最后用了记忆化搜索。

在写记忆化搜索时,我的方法是: 先写爆搜,通过小样例之后再加记忆化。

对于本题而言,我最开始想到的是从第 n 个人倒回去处理,设他在第 x 分钟离开( tix),进行搜索。 dfs(k,g) 表示第 k 个人在时间点 g 之后离开,枚举第 k1 离开时间点后判断上一个人是否会与这个人同车。

但是这样显然时间复杂度与 O(nt) 有关,妥妥的 TLE。

到这里,我们会发现有两条路:往 O(nm) 走或者往 O(t) 走。

接下来,我分析了题目,发现 每一个人至多等待 2×m 分钟 ,因为对于第 i 个人,如果 xti>2m ,表明此时他在的时候车子已经开过一遍了,而他却还是呆在原地,显然不是最优的选择。

所以,我们没有必要去枚举 x ,只需要枚举一个数 k0k2m ,即为等车时间),则第 i 个人发车时间为 ti+k 。这样就避免了因为 t 的范围而引起的超时,方向为 O(nm)

那么接下来如何判断第 i1 个人与第 i 个人是否同车呢?

同样,我们枚举 k ,表示第 i1 个人的等车时间。

  • 如果 ti1+k=ti+k ,表明此时两个人发车时间相同,那么一起走。
  • 如果 ti1+k+mti+k ,表明此时第 i1 个人坐车到达,车子回到人大附中之后第 i 个人还没有发车,那么第 i1 个人直接发车离开。
  • 如果 ti1+k+m>ti+k ,表明第 i1 个人发车走后第 i 个人还要再等,但是这种情况按照上面所述显然不优:
    • 如果第 i1 个人走了之后第 i 个人都还没有到,那么我们完全不需要去理第 i 个人,那么直接发车走人,而且此时肯定是越早走越好,那么我们在枚举 k 的时候已经解决了。
    • 否则,说明第 i1 个人跟第 i 个人在一起等车,但是第 i1 个人走了第 i 个人还呆在那里,之前已经解释过肯定不是最优解,那么也不需要去管。

分析完毕,此时 dfs(k,g) 表示第 k 个人在 tk+g 的时间发车(而不是第 g 时间点发车) 时的最小值。函数中,我们枚举 i0i<2m),进行上述判断,如果满足任意条件,更新答案。

更新答案时,我的方式是函数中令 sum=INF ,满足任一条件时, summin(sum,dfs(k1,i)+g)dfs(k1,i)+g 表示第 k1 个人在 tk1+g 时发车时等待时间最小值,加上第 k 个人等待时间 g ),最后返回 sum 即可。

不要忘记对 t 排序!

暴力代码如下:

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

const int MAXN = 500 + 10;
int n, m, t[MAXN], f[MAXN][MAXN], ans;//f[i][j] 表示第 i 个人在等了 j 时间后发车的最优解

int read()
{
	int sum = 0, fh = 1; char ch = getchar();
	while (ch < '0' || ch > '9') {if (ch == '-') fh = -1; ch = getchar();}
	while (ch >= '0' && ch <= '9') {sum = (sum << 3) + (sum << 1) + (ch ^ 48); ch = getchar();}
	return sum * fh;
}
int Min(int a, int b) {return (a < b) ? a : b;}

int dfs(int k, int g)
{
	if (k == 1) return g;
	int ans = 0x7f7f7f7f;
	for (int i = 0; i < (m << 1); ++i)
	{
		if (t[k - 1] + i == t[k] + g) ans = Min(ans, dfs(k - 1, i) + g);
		if (t[k - 1] + i + m <= t[k] + g) ans = Min(ans, dfs(k - 1, i) + g);
	}
	return ans;
}//暴力dfs

int main()
{
	n = read(), m = read(), ans = 0x7f7f7f7f;
	for (int i = 1; i <= n; ++i) t[i] = read();
	sort(t + 1, t + n + 1); memset(f, -1, sizeof(f));
	for (int i = 0; i< (m << 1); ++i) ans = Min(ans, dfs(n, i));
	printf("%d\n", ans); return 0;
}

接下来加记忆化。其实在写记忆化搜索的时候,我个人认为加记忆化是最简单的, 只要爆搜写好并且写对,加记忆化易如反掌。哪里有 return ,哪里加记忆化 。最后代码如下:

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

const int MAXN = 500 + 10;
int n, m, t[MAXN], f[MAXN][MAXN], ans;//f[i][j] 表示第 i 个人在等了 j 时间后发车的最优解,初始化为 -1。

int read()
{
	int sum = 0, fh = 1; char ch = getchar();
	while (ch < '0' || ch > '9') {if (ch == '-') fh = -1; ch = getchar();}
	while (ch >= '0' && ch <= '9') {sum = (sum << 3) + (sum << 1) + (ch ^ 48); ch = getchar();}
	return sum * fh;
}
int Min(int a, int b) {return (a < b) ? a : b;}

int dfs(int k, int g)
{
	if (f[k][g] != -1) return f[k][g];
	if (k == 1) return f[k][g] = g;
	int ans = 0x7f7f7f7f;
	for (int i = 0; i < (m << 1); ++i)
	{
		if (t[k - 1] + i == t[k] + g) ans = Min(ans, dfs(k - 1, i) + g);
		if (t[k - 1] + i + m <= t[k] + g) ans = Min(ans, dfs(k - 1, i) + g);
	}
	return f[k][g] = ans;
}

int main()
{
	n = read(), m = read(), ans = 0x7f7f7f7f;
	for (int i = 1; i <= n; ++i) t[i] = read();
	sort(t + 1, t + n + 1); memset(f, -1, sizeof(f));
	for (int i = 0; i< (m << 1); ++i) ans = Min(ans, dfs(n, i));
	printf("%d\n", ans); return 0;
}

最后的总结:其实对于普及的 DP ,如果没有好思路时,可以尝试写记忆化搜索,或许就能解出 DP 题而成功的获得高分。

posted @   Plozia  阅读(92)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示