P5017/Noip2018-PJT3 摆渡车 题解
update 2021/1/19:
- 原题解部分 Markdown 和 Latex 炸了,现在已经修复。
- 对题解当中一些表述不明的句子做了进一步的阐述。
- 更换了码风。
首先,看到这道题在普及组 T3 的位置,我们知道只能是 搜索/图论/DP 三者之一。
再结合数据范围我们就可以 根据个人经验 分析出这道题是 DP 题。
但或许是我太弱了,没有想出 DP 怎么写,最后用了记忆化搜索。
在写记忆化搜索时,我的方法是: 先写爆搜,通过小样例之后再加记忆化。
对于本题而言,我最开始想到的是从第 个人倒回去处理,设他在第 分钟离开( ),进行搜索。 表示第 个人在时间点 之后离开,枚举第 离开时间点后判断上一个人是否会与这个人同车。
但是这样显然时间复杂度与 有关,妥妥的 TLE。
到这里,我们会发现有两条路:往 走或者往 走。
接下来,我分析了题目,发现 每一个人至多等待 分钟 ,因为对于第 个人,如果 ,表明此时他在的时候车子已经开过一遍了,而他却还是呆在原地,显然不是最优的选择。
所以,我们没有必要去枚举 ,只需要枚举一个数 ( ,即为等车时间),则第 个人发车时间为 。这样就避免了因为 的范围而引起的超时,方向为 。
那么接下来如何判断第 个人与第 个人是否同车呢?
同样,我们枚举 ,表示第 个人的等车时间。
- 如果 ,表明此时两个人发车时间相同,那么一起走。
- 如果 ,表明此时第 个人坐车到达,车子回到人大附中之后第 个人还没有发车,那么第 个人直接发车离开。
- 如果 ,表明第 个人发车走后第 个人还要再等,但是这种情况按照上面所述显然不优:
- 如果第 个人走了之后第 个人都还没有到,那么我们完全不需要去理第 个人,那么直接发车走人,而且此时肯定是越早走越好,那么我们在枚举 的时候已经解决了。
- 否则,说明第 个人跟第 个人在一起等车,但是第 个人走了第 个人还呆在那里,之前已经解释过肯定不是最优解,那么也不需要去管。
分析完毕,此时 表示第 个人在 第 的时间发车(而不是第 时间点发车) 时的最小值。函数中,我们枚举 (),进行上述判断,如果满足任意条件,更新答案。
更新答案时,我的方式是函数中令 ,满足任一条件时, ( 表示第 个人在 时发车时等待时间最小值,加上第 个人等待时间 ),最后返回 即可。
不要忘记对 排序!
暴力代码如下:
#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 题而成功的获得高分。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具