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

  • 2N80
  • 1Pi,j109
  • 1Ri,j,Di,j109
  • 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,N1

RN,1 RN,N1
D1,1 D1,N

DN1,1 DN1,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

Figure

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{0,h(i,j,n,n)g(i,j)}pi,j 次。因此当 (i,j) 是最后一次使用第一种操作的位置时,(i,j)(n,n) 的最小操作次数就是 c+ni+nj

  现在问题就变成了求 (1,1)(i,j) 的最小操作次数,显然可以沿用上面的思路。为此可以考虑 dp,定义 f(i,j) 表示 (1,1)(i,j) 的最小操作次数,根据最后一次使用第一种操作的位置 {(u,v)1ui,1vj} 进行状态划分,状态转移方程就是 f(i,j)=min(u,v){f(u,v)+c+iu+jv},其中 c=max{0,h(u,v,i,j)g(u,v)}pu,v

  考虑维护 g(i,j) 的最大值,当 f(i,j)>f(u,v)+c+iu+jv 时,直接令 g(i,j)=g(u,v)+cpu,vh(u,v,i,j)。否则如果 f(i,j)=f(u,v)+c+iu+jvg(i,j)=max{g(i,j),g(u,v)+cpu,vh(u,v,i,j)}

  h(i,j,u,v) 可以用 dp 预处理出来,状态转移方程就是 h(i,j,u,v)=min{h(i,j,u1,v)+du1,v,h(i,j,u,v1)+ru,v1}

  AC 代码如下,时间复杂度为 O(n4)

#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 MB 左右。还可以把 h 优化到两维。在枚举 (i,j) 的时候定义 h(u,v) 表示 (u,v)(i,j) 只执行第二、三种操作的最小金额。状态转移方程就是 h(u,v)=min{h(u+1,v)+du,v,h(u,v+1)+ru,v}

  AC 代码如下,时间复杂度为 O(n4)

#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

posted @   onlyblues  阅读(54)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
Web Analytics
点击右上角即可分享
微信分享提示