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 $P_{i,j}$.
- Pay $R_{i,j}$ from his money and move to square $(i,j+1)$.
- Pay $D_{i,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 \leq N \leq 80$
- $1 \leq P_{i,j} \leq 10^9$
- $1 \leq R_{i,j},D_{i,j} \leq 10^9$
- All input values are integers.
Input
The input is given from Standard Input in the following format:
$N$
$P_{1,1}$ $\ldots$ $P_{1,N}$
$\vdots$
$P_{N,1}$ $\ldots$ $P_{N,N}$
$R_{1,1}$ $\ldots$ $R_{1,N-1}$
$\vdots$
$R_{N,1}$ $\ldots$ $R_{N,N-1}$
$D_{1,1}$ $\ldots$ $D_{1,N}$
$\vdots$
$D_{N-1,1}$ $\ldots$ $D_{N-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) \to (n,n)$ 的最小操作次數,不妨先考慮最後一次使用第一種操作的位置,假設為 $(i,j)$。為了保證 $(i,j) \to (n,n)$ 不存在金額為負數的情況,需要在 $(i,j)$ 處停留一定的次數。假設 $h(i,j,n,n)$ 表示 $(i,j) \to (n,n)$ 的所有路徑中只執行第二、三種操作的最小金額,$g(i,j)$ 表示 $(1,1) \to (i,j)$ 還剩的金額,那麼在 $(i,j)$ 處至少要停留 $c = \left\lceil \frac{\max\{{0, \, h(i,j,n,n) - g(i,j)}\}}{p_{i,j}} \right\rceil$ 次。因此當 $(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