AtCoder 415-E 题解
题目传送门
\(\color{black}{AtCoder 415 E}\)
前言
其实没有特别的难了,就是要注意一些小坑。
题面
有一个人小A, 被困在了一个网格图里,网格图的第i行j列位于\((i,j)\),初始时\((i,j)\)位置上有\(A_{i,j}\)个金币,第一天开始前小A拥有 \(x\) 枚金币,并且小A在位置\((1,1)\)
在网格图里的每一天,对于第 \(k\) 天 \((1 \leq k \leq H + W - 1)\) ,小A会按顺序做以下操作:
- 首先小A会拾取当前位置的金币
- 然后小A会花费 \(P_k\) 个金币买吃的,如果买不起就Game over!Game over就直接结束!
- 如果 \(k < (H + W - 1)\)那么小A会在向下和向右任意选取一个方向来移动(不能移出网格图!)
- 如果 \(k = (H + W - 1)\),并且小A还没Game over,那么小A就可以逃离!
小B作为一名管理钱钱的上帝,可以决定 \(x\) 的取值,因为小B很善良但也喜欢钱,所以他打算:
在满足小A至少有一种逃离网格图的情况下,尽可能的使 \(x\) 的取值小。
请问,如果小B绝顶聪明,一定会在满足小A可以逃离的情况下使 \(x\) 最小,\(x\)究竟是多少?
思路
首先注意到题目要求满足条件的最小值,且题目满足二分性,可以考虑二分。
然后考虑如果我们已经知道了 \(x\) 那么该如何算出是否能够逃出网格图?
首先对于 \(x\),我们可以让 \(A_{1,1}\) 临时加上 \(x\) , 这样就不用在接下来考虑 \((1,1)\) 时额外考虑 \(x\) 了。
我们记\(dp_{i,j}\) 表示当我们到达了 \((i,j)\)并过完了相应的那一天,那么我们还能剩下多少个金币。
如果到 \((i,j)\) 一定会Game over 或者不能存活着到 \((i,j)\),那么我们定义\(dp_{i,j} = -\inf\)
那么显然:\(dp_{i,j} = \max(dp_{i-1,j}, dp_{i,j-1}) + A_{i,j}- P_{i+j-1}\)
如果算完\(dp_{i,j}\)之后发现:\(dp_{i,j} \leq -1\) ,那么说明如果到 \((i,j)\) 一定会Game over,我们把\(dp_{i,j}\) 赋值为 \(-\inf\)
因为:\((A_{i,j} < inf)\) 所以如果 \(\max(dp_{i-1,j}, dp_{i,j-1}) = -\inf\) 那么算完之后一定还是 \(\leq -1\)
最后如果\(dp_{H,W} \neq -1\) 那么说明可以逃出网格,否则不行。
还有我们希望所有\(dp_{i,0} (1 \leq i \leq H)\) 和 \(dp_{0,j} (1 \leq j \leq W)\)都不要干涉到我们取\(\max\),所以初始化为 \(- \inf\)
所以做法就是:
二分找最小的\(x\),\(check\)函数里面求如果初始时有 \(mid\) 个金币,那么是否可以逃出网格图。
注意:
1、由于题目只告诉你了\(H \times W \leq 2 \times 10^5\) ,所以这里我建议使用vector<int>[]来进行存储(千万不要用map<pair<int, int> >! map的时间复杂度有一个\(\log\)!!!!)!
2、\(dp\)数组记得初始化!
3、如果考虑的是 \((1,1)\),不需要对\(dp_{i-1,j}\) 和 \(dp_{i,j-1})\) 取 \(\max\) ,因为不存在哪一个点到\((1,1)\)!
4、如果你用上面我的题解的方法,那别私自把失败改成\(-1\),不然\(A_{i,j} + (-1) - P_{i+j-1}\)可能大于\(-1\)!
4、\(A_{1,1}\) 加上 \(mid\) 时暂时的,最后要记得 \(A_{1,1}\)减去\(mid\)!
5、如果你像我一样选择了vector<int>[],且在\(check\)函数中时使用了从\(1\)开始的下标,那么输入的时候要用0和长度为\(W\)的全\(0\)vector来填充!
6、不开 long long 见祖宗!
7、二分时如果你像我一样使用\((l<r-1)\)且输出的是\(r\),那么\(l\)要开到\(-1\)!(因为\(r\)可以取到\(0\)!)
8、二分初始时\(r\)要开大点,\(10^9\)不够!
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
int h, w;
vector<int> mp[200001]; // 其实就是题解里的 A
vector<int> dp[200001]; // dp数组
int p[400001]; // p数组
int f(int r, int c) // 从A的位置找到p的位置
{
return r + c - 1;
}
bool check(int mid) // check函数
{
mp[1][1] += mid; // 临时加上 mid
for (int i = 0; i <= h; i++)
{
for (int j = 0; j <= w; j++)
{
dp[i][j] = -0x3f3f3f3f; // 初始化
}
}
for (int i = 1; i <= h; i++)
{
for (int j = 1; j <= w; j++)
{
int x = 0;
if (i > 1 || j > 1) x = max(dp[i][j - 1], dp[i - 1][j]); // (1,1) 不需要找“从哪里来”
int t = x + mp[i][j]; // 拿取这个格子的金币
t -= p[f(i, j)]; // 买吃的
if (t < 0) // 买不起就Game Over了
{
t = -0x3f3f3f3f; // 该回-inf
}
dp[i][j] = t; // 记录下来
}
}
mp[1][1] -= mid; // 记得减回来
return (dp[h][w] >= 0);
}
signed main()
{
cin >> h >> w;
for (int j = 0; j <= w; j++) // 先用0补一行
{
mp[0].push_back(0);
dp[0].push_back(0);
}
for (int i = 1; i <= h; i++)
{
mp[i].push_back(0); // 用0补一列
dp[i].push_back(0); // 用0补一列
for (int j = 1; j <= w; j++)
{
int x;
cin >> x;
mp[i].push_back(x); // 用x填充mp数组
dp[i].push_back(0); // 把0填充dp数组
}
}
for (int i = 1; i <= h + w - 1; i++)
{
cin >> p[i]; // 输入p数组
}
int l = -1, r = 1e18; // l记得开到-1,r记得开大一点
while (l < r - 1)
{
int mid = (l + r) / 2;
if (check(mid))
{
r = mid;
}
else
{
l = mid;
}
}
cout << r << endl; // 最后输出~
return 0;
}
完结撒花 ✿✿ヽ(°▽°)ノ✿

浙公网安备 33010602011771号