信息学奥赛一本通 ybt 1940:【07NOIP普及组】守望者的逃离 | 洛谷 P1095 [NOIP 2007 普及组] 守望者的逃离
【题目链接】
ybt 1940:【07NOIP普及组】守望者的逃离
洛谷 P1095 [NOIP 2007 普及组] 守望者的逃离
【题目难度】:B
【题目考点】
1. 动态规划
2. 贪心
【解题思路】
非正解解法:搜索
每步尝试进行等待、闪烁、跑步三种行动,搜索所有可能的行动方案。
如果存在移动距离大于等于s的情况,则记录该情况使用的时间。
同时维护使用时间为t时移动的最大距离。最后输出结果。
该方法可以得30分。
正解解法:动态规划
现在考虑以下两种走法:
- 等待恢复魔法,有足够魔法就使用闪烁
- 一直跑步
首先假设时间无限长,使用哪种走法更合适。
每秒恢复魔法4点,每次闪烁消耗魔法10点,如果仅靠恢复的魔法进行闪烁,且有魔法就是闪烁,那么守望者的行动顺序为:
| 时刻(第几秒) | 操作 | 操作后剩余魔法 | 移动动距离 |
|---|---|---|---|
| 1 | 休息 | 4 | 0 |
| 2 | 休息 | 8 | 0 |
| 3 | 休息 | 12 | 0 |
| 4 | 闪烁 | 2 | 60 |
| 5 | 休息 | 6 | 60 |
| 6 | 休息 | 10 | 60 |
| 7 | 闪烁 | 0 | 120 |
根据上表可知,守望者完全靠休息恢复魔法和闪烁技能,每7秒钟可以前进120米。
而如果每秒都跑步,7秒可以前进7∗17=1197*17=1197∗17=119米,比休息+闪烁的前进方案前进距离更短。
因此,如果时间足够长,需要走的距离足够远,应该尽量使用休息+闪烁的方案前进。
而如果已经接近终点,比如此时距离终点20米,但魔法值为0。如果想使用闪烁,需要休息3秒后再闪烁,需要4秒才能到达终点。而如果跑步,2秒后就可以到达终点。因此当距离较短时,应该跑步前进。
基于上述理解,可以预处理出数组fff,fif_ifi表示前iii秒只进行休息+闪烁可以前进的最大距离。
设变量mmm表示守望者的剩余魔法值。在第iii秒时:
- 如果m≥10m\ge 10m≥10,那么可以使用闪烁,就使用。第iii秒移动的距离比第i−1i-1i−1秒增加60,即fi=fi−1+60f_i = f_{i-1}+60fi=fi−1+60,同时剩余魔法值mmm减少10。
- 如果m<10m < 10m<10,那么只能休息,第i秒移动的距离与第i−1i-1i−1秒移动距离相同,即fi=fi−1f_i = f_{i-1}fi=fi−1,同时剩余魔法值mmm增加10。
设动规状态定义dpdpdp,dpidp_idpi表示前iii秒进行的所有行动方案中,前进距离最大的方案的前进距离。即前i秒可以前进的最大距离。
初值dp0=0dp_0 = 0dp0=0。
分析状态转移方程:根据第i秒是否跑步分割策略集合。
- 情况1:在前i−1i-1i−1秒前进距离最大的方案基础上,在第iii秒跑步前进,这样做前iii秒可以行进的距离为dpi−1+17dp_{i-1}+17dpi−1+17
- 情况2:由于跑步前进可能不如休息+闪烁移动的距离更大,因此前iii秒也可以选择只进行休息+闪烁的行动方案,前iii秒的行进距离为fif_ifi
- 两种情况取最大值。
因此:dpi=max{dpi−1+17,fi}dp_i = \max\{dp_{i-1}+17, f_i\}dpi=max{dpi−1+17,fi}
递推求出dpdpdp数组的同时,看前iii秒守望者移动的最大距离dpidp_idpi是否大于等于总距离sss。如果dpi≥sdp_i\ge sdpi≥s,那么守望者只需要iii秒时间就可以逃出洞窟,输出Yes和iii,程序结束。
递推结束后,如果程序没有结束,那么守望者在ttt秒时间内无法逃出。ttt秒时间守望者可以前进的最大距离为dptdp_tdpt,输出No和dptdp_tdpt。
递推变迭代
由于在递推求fff和dpdpdp的递推式中,只存在第i−1i-1i−1项的值fi−1f_{i-1}fi−1和dpi−1dp_{i-1}dpi−1,因此可以降维,变递推为迭代。
比如将f[i] = f[i-1]+60变为f = f+60。等号右边的f表示f[i-1],等号左边的f表示f[i]。
这样依然可以正确求出结果,并且可以优化代码的空间复杂度。但变量的意义、整个算法的概念没有变。
【题解代码】
非正解解法1:搜索 30pt
#include <bits/stdc++.h>
using namespace std;
int m, s, t, ansTime = 1e9, ansDis;
bool hasAns;
void dfs(int time, int ma, int dis)//走了time秒时间,剩余魔法ma,已走距离dis
{if(dis >= s){hasAns = true;ansTime = min(time, ansTime);return; }if(time == t){ansDis = max(ansDis, dis);return;}dfs(time+1, ma+4, dis);//休息if(ma >= 10)dfs(time+1, ma-10, dis+60);//闪烁dfs(time+1, ma, dis+17);//跑步
}
int main()
{cin >> m >> s >> t;dfs(0, m, 0);if(hasAns)cout << "Yes\n" << ansTime;elsecout << "No\n" << ansDis;return 0;
}
正解解法:动态规划
- 写法1:基本写法
#include<bits/stdc++.h>
using namespace std;
#define N 300005
int f[N];//f[i]:前i秒只使用闪烁能走到的最大的距离
int dp[N];//dp[i]:前i秒能走到的最大的距离
int main()
{int m, s, t;cin >> m >> s >> t;for(int i = 1; i <= t; ++i){if(m >= 10) {f[i] = f[i-1]+60;m -= 10;}else{f[i] = f[i-1];m += 4;}}for(int i = 1; i <= t; ++i){dp[i] = max(f[i], dp[i-1]+17);if(dp[i] >= s){cout << "Yes" << '\n' << i;return 0;}}cout << "No\n" << dp[t];return 0;
}
- 写法2:递推变迭代
#include<bits/stdc++.h>
using namespace std;
int main()
{int m, s, t, f, dp;//f:前i秒只使用闪烁能走到的最大的距离 dp:前i秒能走到的最大的距离;cin >> m >> s >> t;for(int i = 1; i <= t; ++i){if(m >= 10){f = f+60;m -= 10;}elsem += 4;dp = max(f, dp+17);if(dp >= s){cout << "Yes" << '\n' << i;return 0;}}cout << "No\n" << dp;return 0;
}
