阿里马马的逃跑路线是一条直线,需要从坐标为 1 的藏宝库逃到坐标为 n 的出口,他每次只能移动到相邻的坐标,每次移动需要花费 1 单位时间,在这条路线中,任意坐标 i 上都有一个数字 a i a_i ai。
-
a i = 0 \mathrm{a_i= 0} ai=0 时,表示此处为安全区,可以选择在此处疗伤,恢复生命值直至上限(额外花费的时间与恢复的生命值大小相同)或 不恢复生命值,继续前行(无额外花费时间)。请注意,疗伤过程一旦中途停止就会功亏一篑,所以不存在恢复部分生命值的情况。
-
a i > 0 \mathrm{a_i> 0} ai>0 时,表示此处为陷阱,a_i 的值即为通过此处损失的生命值。已知阿里马马初始生命值为其生命值上限,且藏宝库与出口都为安全区 ( a 1 = a n = 0 ) \mathrm{( a_1= a_n= 0) } (a1=an=0) ,请判断阿里马马是否
能够顺利逃走 (逃跑途中生命值小于等于零视为逃跑失败),并计算逃跑需要的最短时间。
输入描述:
第一行为两个整数 n , m ( 2 ≤ n ≤ 1 0 5 , 1 ≤ m ≤ 1 0 9 ) \mathrm{n, m( 2\leq n\leq 10^5, 1\leq m\leq 10^9) } n,m(2≤n≤105,1≤m≤109) 表示逃跑路线长度与阿里马马初始生命值。下一行为 n 个整数,第 i 个数表示 a i ( 0 ≤ a i ≤ 1 0 9 , a 1 = a n = 0 ) \mathrm{a_i( 0\leq a_i\leq 10^9, a_1= a_n= 0) } ai(0≤ai≤109,a1=an=0) 的值。
输出描述:
若阿里马马不能逃走,则输出 NO , 否则输出他逃走需要花费的最短时间。
示例1
输入:
5 3
0 1 2 3 0
输出:
NO
说明:
阿里马马的初始生命值为3,显然1+2+3>3,阿里马马不能逃脱。
示例2
输入:
5 8
0 6 0 6 0
输出:
10
说明:
阿里马马初始生命值为8,在到达坐标2时生命值变为2,在坐标3选择恢复所有生命值(额外花费6单位时间),在坐标4生命值变为2,最终到达坐标5,其逃走所需要的最短时间为6十4=10
分析:
举个例子,假设m很大,有a数组:0 2 3 1 0 2 3 1 …… 0 1 2 3
我们发现无论是在第一个0回血还是第二个0回血,最终都会等于前面陷阱所消耗的血量和,所以,只要没有一段连续的陷阱使其直接死亡,我们只需要找到最后一个回血点,并使得最后一个回血点之后的陷阱量尽可能大但是不超过m,这样我们就可以减少回血了从而节约时间。找到最后一个回血点只需要用后序查找即可。
下面是完整代码:
// Problem: 阿里马马与四十大盗
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/contest/72980/D
// Memory Limit: 524288 MB
// Time Limit: 2000 ms
//
#include <iostream>
#include <queue>
#include <cstring>
#include <map>
#include <algorithm>
#include <vector>
#include <set>
#include <cmath>
#include <stack>
#include <random>
#include <ctime>
#define ll long long
#define ull unsigned long long
#define ios ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define __int128_t int128
using namespace std;
typedef pair<int, int> PII;
const int INF = 0x3f3f3f3f3f3f3f3f;
const int N = 1e5 + 10;
int n, m;
int a[N];
ll res = 0;
int main()
{
cin >> n >> m;
res = n-1;//通过一个坐标消耗的时间
for(int i = 1; i<= n;i++){
cin >> a[i];
res += a[i];
}
for(int i = 1; i<= n;i++){
ll sum = 0;
int j = i + 1;
while(a[j]){
sum += a[j];
j++;
}
if(sum >= m){
//这一段陷阱过后会直接死亡
cout << "NO" << endl;
return 0;
}
i = j - 1;
}
ll sv = 0,sum = 0;
for(int i = n; i>= 1;i--){
sum += a[i];
if(!a[i] && sum < m){
//找到最后一个回血点并且sum<m才不会死亡
sv =sum;
}
}
res -= sv;
cout << res << endl;
return 0;
}
这段代码的逻辑是:
- 假设阿里马马移动到位置 i,不管这个位置是安全区还是陷阱。
- 从下一个位置开始检查 (j = i +1),累加连续陷阱的伤害值,直到遇到下一个安全区(a[j] == 0)。
- 如果这个连续陷阱区域的总伤害值大于等于阿里马马的生命值m,则输出"NO",因为阿里马马无法通过这段区域而不死亡。
- 如果可以通过,跳过这段区域,即将 i 更新为这段连续陷阱区域的最后一个位置。