F - Earn to Advance
F - Earn to Advance
Problem Statement
There is a grid with N rows and N columns. Let (i,j) denote the square at the i-th row from the top and j-th column from the left.
Takahashi is initially at square (1,1) with zero money.
When Takahashi is at square (i,j), he can perform one of the following in one action:
- Stay at the same square and increase his money by Pi,j.
- Pay Ri,j from his money and move to square (i,j+1).
- Pay Di,j from his money and move to square (i+1,j).
He cannot make a move that would make his money negative or take him outside the grid.
If Takahashi acts optimally, how many actions does he need to reach square (N,N)?
Constraints
- 2≤N≤80
- 1≤Pi,j≤109
- 1≤Ri,j,Di,j≤109
- All input values are integers.
Input
The input is given from Standard Input in the following format:
N
P1,1 … P1,N
⋮
PN,1 … PN,N
R1,1 … R1,N−1
⋮
RN,1 … RN,N−1
D1,1 … D1,N
⋮
DN−1,1 … DN−1,N
Output
Print the answer.
Sample Input 1
3
1 2 3
3 1 2
2 1 1
1 2
4 3
4 2
1 5 7
5 3 3
Sample Output 1
8
It is possible to reach square (3,3) in eight actions as follows:
- Stay at square (1,1) and increase money by 1. His money is now 1.
- Pay 1 money and move to square (2,1). His money is now 0.
- Stay at square (2,1) and increase money by 3. His money is now 3.
- Stay at square (2,1) and increase money by 3. His money is now 6.
- Stay at square (2,1) and increase money by 3. His money is now 9.
- Pay 4 money and move to square (2,2). His money is now 5.
- Pay 3 money and move to square (3,2). His money is now 2.
- Pay 2 money and move to square (3,3). His money is now 0.
Sample Input 2
3
1 1 1
1 1 1
1 1 1
1000000000 1000000000
1000000000 1000000000
1000000000 1000000000
1000000000 1000000000 1000000000
1000000000 1000000000 1000000000
Sample Output 2
4000000004
解题思路
要求 (1,1)→(n,n) 的最小操作次数,不妨先考虑最后一次使用第一种操作的位置,假设为 (i,j)。为了保证 (i,j)→(n,n) 不存在金额为负数的情况,需要在 (i,j) 处停留一定的次数。假设 h(i,j,n,n) 表示 (i,j)→(n,n) 的所有路径中只执行第二、三种操作的最小金额,g(i,j) 表示 (1,1)→(i,j) 还剩的金额,那么在 (i,j) 处至少要停留 c=⌈max 次。因此当 (i,j) 是最后一次使用第一种操作的位置时,(i,j) \to (n,n) 的最小操作次数就是 c + n-i + n-j。
现在问题就变成了求 (1,1) \to (i,j) 的最小操作次数,显然可以沿用上面的思路。为此可以考虑 dp,定义 f(i,j) 表示 (1,1) \to (i,j) 的最小操作次数,根据最后一次使用第一种操作的位置 \{ (u,v) \mid 1 \leq u \leq i, 1 \leq v \leq j \} 进行状态划分,状态转移方程就是 f(i,j) = \min\limits_{(u,v)}\left\{{f(u,v) + c + i-u + j-v}\right\},其中 c = \left\lceil \frac{\max\{{0, \, h(u,v,i,j) - g(u,v)}\}}{p_{u,v}} \right\rceil。
考虑维护 g(i,j) 的最大值,当 f(i,j) > f(u,v) + c + i-u + j-v 时,直接令 g(i,j) = g(u,v) + c \cdot p_{u,v} - h(u,v,i,j)。否则如果 f(i,j) = f(u,v) + c + i-u + j-v,g(i,j) = \max\{{g(i,j), \, g(u,v) + c \cdot p_{u,v} - h(u,v,i,j)}\}。
h(i,j,u,v) 可以用 dp 预处理出来,状态转移方程就是 h(i,j,u,v) = \min\left\{h(i,j,u-1,v) + d_{u-1,v}, \, h(i,j,u,v-1) + r_{u,v-1}\right\}。
AC 代码如下,时间复杂度为 O(n^4):
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 85;
int p[N][N], r[N][N], d[N][N];
LL f[N][N], g[N][N], h[N][N][N][N];
int main() {
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
scanf("%d", &p[i][j]);
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j < n; j++) {
scanf("%d", &r[i][j]);
}
}
for (int i = 1; i < n; i++) {
for (int j = 1; j <= n; j++) {
scanf("%d", &d[i][j]);
}
}
memset(h, 0x3f, sizeof(h));
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
h[i][j][i][j] = 0;
for (int u = i; u <= n; u++) {
for (int v = j; v <= n; v++) {
if (u == i && v == j) continue;
h[i][j][u][v] = min(h[i][j][u - 1][v] + d[u - 1][v], h[i][j][u][v - 1] + r[u][v - 1]);
}
}
}
}
memset(f, 0x3f, sizeof(f));
f[1][1] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
for (int u = 1; u <= i; u++) {
for (int v = 1; v <= j; v++) {
LL c = (max(0ll, h[u][v][i][j] - g[u][v]) + p[u][v] - 1) / p[u][v];
if (f[i][j] > f[u][v] + c + i - u + j - v) {
f[i][j] = f[u][v] + c + i - u + j - v;
g[i][j] = g[u][v] + p[u][v] * c - h[u][v][i][j];
}
else if (f[i][j] == f[u][v] + c + i - u + j - v) {
g[i][j] = max(g[i][j], g[u][v] + p[u][v] * c - h[u][v][i][j]);
}
}
}
}
}
printf("%lld", f[n][n]);
return 0;
}
上面代码所需的内存是 400 \text{ MB} 左右。还可以把 h 优化到两维。在枚举 (i,j) 的时候定义 h(u,v) 表示 (u,v) \to (i,j) 只执行第二、三种操作的最小金额。状态转移方程就是 h(u,v) = \min\left\{{h(u+1,v) + d_{u,v}, \, h(u,v+1) + r_{u,v}}\right\}。
AC 代码如下,时间复杂度为 O(n^4):
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 85;
int p[N][N], r[N][N], d[N][N];
LL f[N][N], g[N][N], h[N][N];
int main() {
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
scanf("%d", &p[i][j]);
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j < n; j++) {
scanf("%d", &r[i][j]);
}
}
for (int i = 1; i < n; i++) {
for (int j = 1; j <= n; j++) {
scanf("%d", &d[i][j]);
}
}
memset(f, 0x3f, sizeof(f));
f[1][1] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
memset(h, 0x3f, sizeof(h));
h[i][j] = 0;
for (int u = i; u; u--) {
for (int v = j; v; v--) {
if (u == i && v == j) continue;
h[u][v] = min(h[u + 1][v] + d[u][v], h[u][v + 1] + r[u][v]);
}
}
for (int u = 1; u <= i; u++) {
for (int v = 1; v <= j; v++) {
LL c = (max(0ll, h[u][v] - g[u][v]) + p[u][v] - 1) / p[u][v];
if (f[i][j] > f[u][v] + c + i - u + j - v) {
f[i][j] = f[u][v] + c + i - u + j - v;
g[i][j] = g[u][v] + p[u][v] * c - h[u][v];
}
else if (f[i][j] == f[u][v] + c + i - u + j - v) {
g[i][j] = max(g[i][j], g[u][v] + p[u][v] * c - h[u][v]);
}
}
}
}
}
printf("%lld", f[n][n]);
return 0;
}
参考资料
Editorial - Toyota Programming Contest 2024#3(AtCoder Beginner Contest 344):https://atcoder.jp/contests/abc344/editorial/9494
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/18064351
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步