牛客题解 | 龙与地下城游戏问题

题目## 题目

题目链接

解题思路

这是一个动态规划问题,但需要从终点往起点推导:

  1. 创建一个 d p dp dp 数组, d p [ i ] [ j ] dp[i][j] dp[i][j] 表示到达位置 ( i , j ) (i,j) (i,j) 时需要的最小血量
  2. 从右下角开始,逆向推导到左上角
  3. 对于每个位置 ( i , j ) (i,j) (i,j)
    • 如果是终点,则 d p [ i ] [ j ] = max ⁡ ( 1 , 1 − m a p [ i ] [ j ] ) dp[i][j] = \max(1, 1-map[i][j]) dp[i][j]=max(1,1map[i][j])
    • 否则, d p [ i ] [ j ] = max ⁡ ( 1 , min ⁡ ( d p [ i + 1 ] [ j ] , d p [ i ] [ j + 1 ] ) − m a p [ i ] [ j ] ) dp[i][j] = \max(1, \min(dp[i+1][j], dp[i][j+1]) - map[i][j]) dp[i][j]=max(1,min(dp[i+1][j],dp[i][j+1])map[i][j])
  4. 最终 d p [ 0 ] [ 0 ] dp[0][0] dp[0][0] 就是所需的最小初始血量

代码

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int minInitialHealth(vector<vector<int>>& map) {
    int n = map.size();
    int m = map[0].size();
    vector<vector<int>> dp(n, vector<int>(m));
    
    // 初始化终点
    dp[n-1][m-1] = max(1, 1 - map[n-1][m-1]);
    
    // 初始化最后一列
    for(int i = n-2; i >= 0; i--) {
        dp[i][m-1] = max(1, dp[i+1][m-1] - map[i][m-1]);
    }
    
    // 初始化最后一行
    for(int j = m-2; j >= 0; j--) {
        dp[n-1][j] = max(1, dp[n-1][j+1] - map[n-1][j]);
    }
    
    // 填充其他位置
    for(int i = n-2; i >= 0; i--) {
        for(int j = m-2; j >= 0; j--) {
            int minNext = min(dp[i+1][j], dp[i][j+1]);
            dp[i][j] = max(1, minNext - map[i][j]);
        }
    }
    
    return dp[0][0];
}

int main() {
    int n, m;
    cin >> n >> m;
    vector<vector<int>> map(n, vector<int>(m));
    
    for(int i = 0; i < n; i++) {
        for(int j = 0; j < m; j++) {
            cin >> map[i][j];
        }
    }
    
    cout << minInitialHealth(map) << endl;
    return 0;
}
import java.util.Scanner;

public class Main {
    public static int minInitialHealth(int[][] map) {
        int n = map.length;
        int m = map[0].length;
        int[][] dp = new int[n][m];
        
        // 初始化终点
        dp[n-1][m-1] = Math.max(1, 1 - map[n-1][m-1]);
        
        // 初始化最后一列
        for(int i = n-2; i >= 0; i--) {
            dp[i][m-1] = Math.max(1, dp[i+1][m-1] - map[i][m-1]);
        }
        
        // 初始化最后一行
        for(int j = m-2; j >= 0; j--) {
            dp[n-1][j] = Math.max(1, dp[n-1][j+1] - map[n-1][j]);
        }
        
        // 填充其他位置
        for(int i = n-2; i >= 0; i--) {
            for(int j = m-2; j >= 0; j--) {
                int minNext = Math.min(dp[i+1][j], dp[i][j+1]);
                dp[i][j] = Math.max(1, minNext - map[i][j]);
            }
        }
        
        return dp[0][0];
    }
    
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();
        int[][] map = new int[n][m];
        
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < m; j++) {
                map[i][j] = sc.nextInt();
            }
        }
        
        System.out.println(minInitialHealth(map));
        sc.close();
    }
}
def min_initial_health(map_grid):
    n, m = len(map_grid), len(map_grid[0])
    dp = [[0] * m for _ in range(n)]
    
    # 初始化终点
    dp[n-1][m-1] = max(1, 1 - map_grid[n-1][m-1])
    
    # 初始化最后一列
    for i in range(n-2, -1, -1):
        dp[i][m-1] = max(1, dp[i+1][m-1] - map_grid[i][m-1])
    
    # 初始化最后一行
    for j in range(m-2, -1, -1):
        dp[n-1][j] = max(1, dp[n-1][j+1] - map_grid[n-1][j])
    
    # 填充其他位置
    for i in range(n-2, -1, -1):
        for j in range(m-2, -1, -1):
            min_next = min(dp[i+1][j], dp[i][j+1])
            dp[i][j] = max(1, min_next - map_grid[i][j])
    
    return dp[0][0]

n, m = map(int, input().split())
map_grid = []
for _ in range(n):
    row = list(map(int, input().split()))
    map_grid.append(row)

print(min_initial_health(map_grid))

算法及复杂度

  • 算法:动态规划(逆向推导)
  • 时间复杂度: O ( n × m ) \mathcal{O}(n\times m) O(n×m),需要遍历整个地图
  • 空间复杂度: O ( n × m ) \mathcal{O}(n\times m) O(n×m),需要一个二维 d p dp dp 数组

题目链接

解题思路

  1. 核心思想:

    • 使用状态压缩DP解决,状态表示为 ( i , s t a t e ) (i, state) (i,state),其中 i i i 表示当前点, s t a t e state state 表示已访问点集合
    • d p [ i ] [ s t a t e ] dp[i][state] dp[i][state] 表示当前在点 i i i,已经访问了 s t a t e state state 表示的点集合时的最小花费
    • 通过二进制表示已访问的点集合
  2. 状态转移:

    • 对于当前状态 ( i , s t a t e ) (i, state) (i,state),枚举下一个要访问的点 j j j
    • d p [ j ] [ s t a t e ∣ ( 1 < < j ) ] = min ⁡ ( d p [ j ] [ s t a t e ∣ ( 1 < < j ) ] , d p [ i ] [ s t a t e ] + d i s t [ i ] [ j ] ) dp[j][state|(1<<j)] = \min(dp[j][state|(1<<j)], dp[i][state] + dist[i][j]) dp[j][state(1<<j)]=min(dp[j][state(1<<j)],dp[i][state]+dist[i][j])

代码

#include <bits/stdc++.h>
using namespace std;

const int N = 205;
const int M = 16;
const int INF = 0x3f3f3f3f;

int n, m, R;
int dist[N][N];
int points[M];
long long dp[M][1 << M];

void floyd() {
    for(int k = 1; k <= n; k++) {
        for(int i = 1; i <= n; i++) {
            for(int j = 1; j <= n; j++) {
                if(dist[i][k] != INF && dist[k][j] != INF) {
                    dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
                }
            }
        }
    }
}

int main() {
    cin >> n >> m >> R;
    for(int i = 0; i < R; i++) {
        cin >> points[i];
    }
    
    // 初始化距离数组
    memset(dist, 0x3f, sizeof(dist));
    for(int i = 1; i <= n; i++) {
        dist[i][i] = 0;
    }
    
    // 读入边
    for(int i = 0; i < m; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        dist[u][v] = min(dist[u][v], w);
        dist[v][u] = min(dist[v][u], w);
    }
    
    // Floyd求最短路
    floyd();
    
    // 状态压缩DP
    memset(dp, 0x3f, sizeof(dp));
    int ALL = (1 << R) - 1;
    
    // 初始化:从任意一点开始
    for(int i = 0; i < R; i++) {
        dp[i][1 << i] = 0;
    }
    
    // 状态转移
    for(int state = 0; state < (1 << R); state++) {
        for(int i = 0; i < R; i++) {
            if(!(state & (1 << i))) continue;
            for(int j = 0; j < R; j++) {
                if(state & (1 << j)) continue;
                int next_state = state | (1 << j);
                dp[j][next_state] = min(dp[j][next_state], 
                    dp[i][state] + dist[points[i]][points[j]]);
            }
        }
    }
    
    // 找最小值
    long long ans = INF;
    for(int i = 0; i < R; i++) {
        ans = min(ans, dp[i][ALL]);
    }
    
    cout << ans << endl;
    
    return 0;
}
import java.util.*;

public class Main {
    static final int N = 205;
    static final int M = 16;
    static final int INF = 0x3f3f3f3f;
    static int n, m, R;
    static int[][] dist = new int[N][N];
    static int[] points = new int[M];
    static long[][] dp = new long[M][1 << M];
    
    static void floyd() {
        for(int k = 1; k <= n; k++) {
            for(int i = 1; i <= n; i++) {
                for(int j = 1; j <= n; j++) {
                    if(dist[i][k] != INF && dist[k][j] != INF) {
                        dist[i][j] = Math.min(dist[i][j], dist[i][k] + dist[k][j]);
                    }
                }
            }
        }
    }
    
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        m = sc.nextInt();
        R = sc.nextInt();
        
        // 读入需要访问的点
        for(int i = 0; i < R; i++) {
            points[i] = sc.nextInt();
        }
        
        // 初始化距离数组
        for(int i = 1; i <= n; i++) {
            Arrays.fill(dist[i], INF);
            dist[i][i] = 0;
        }
        
        // 读入边
        for(int i = 0; i < m; i++) {
            int u = sc.nextInt();
            int v = sc.nextInt();
            int w = sc.nextInt();
            dist[u][v] = Math.min(dist[u][v], w);
            dist[v][u] = Math.min(dist[v][u], w);
        }
        
        // Floyd求最短路
        floyd();
        
        // 初始化dp数组
        for(int i = 0; i < M; i++) {
            Arrays.fill(dp[i], INF);
        }
        
        // 初始状态:从任意点开始
        for(int i = 0; i < R; i++) {
            dp[i][1 << i] = 0;
        }
        
        // 状态压缩DP
        int ALL = (1 << R) - 1;
        for(int state = 0; state < (1 << R); state++) {
            for(int i = 0; i < R; i++) {
                if((state & (1 << i)) == 0) continue;  // i不在当前状态中
                for(int j = 0; j < R; j++) {
                    if((state & (1 << j)) != 0) continue;  // j已经访问过
                    int nextState = state | (1 << j);
                    dp[j][nextState] = Math.min(dp[j][nextState], 
                        dp[i][state] + dist[points[i]][points[j]]);
                }
            }
        }
        
        // 找最小值
        long ans = INF;
        for(int i = 0; i < R; i++) {
            ans = Math.min(ans, dp[i][ALL]);
        }
        
        System.out.println(ans);
        
        sc.close();
    }
}
import sys
input = sys.stdin.readline  # 优化输入

def floyd(n, edges, dist):
    """Floyd算法求任意两点间最短路"""
    for k in range(1, n+1):
        for i in range(1, n+1):
            for j in range(1, n+1):
                if dist[i][k] != INF and dist[k][j] != INF:
                    dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])

def main():
    # 输入
    n, m, R = map(int, input().split())
    points = list(map(int, input().split()))
    
    # 初始化距离数组
    global INF
    INF = float('inf')
    dist = [[INF] * (n+1) for _ in range(n+1)]
    for i in range(1, n+1):
        dist[i][i] = 0
    
    # 读入边
    for _ in range(m):
        u, v, w = map(int, input().split())
        dist[u][v] = min(dist[u][v], w)
        dist[v][u] = min(dist[v][u], w)
    
    # Floyd求最短路
    floyd(n, None, dist)
    
    # 状态压缩DP
    ALL = (1 << R) - 1
    dp = [[INF] * (1 << R) for _ in range(R)]
    
    # 初始化:从任意点开始
    for i in range(R):
        dp[i][1 << i] = 0
    
    # 状态转移
    for state in range(ALL + 1):
        for i in range(R):
            if not (state & (1 << i)):  # i不在当前状态中
                continue
            cur = dp[i][state]
            if cur == INF:
                continue
            for j in range(R):
                if state & (1 << j):  # j已经访问过
                    continue
                next_state = state | (1 << j)
                dp[j][next_state] = min(dp[j][next_state], 
                                      cur + dist[points[i]][points[j]])
    
    # 找最小值
    ans = min(dp[i][ALL] for i in range(R))
    print(ans)

if __name__ == "__main__":
    main()

算法及复杂度

  • 算法:Floyd最短路 + 状态压缩DP
  • 时间复杂度: O ( n 3 + R ⋅ 2 R ) \mathcal{O}(n^3 + R \cdot 2^R) O(n3+R2R)
    • Floyd算法: O ( n 3 ) O(n^3) O(n3)
    • 状态压缩DP: O ( R ⋅ 2 R ) O(R \cdot 2^R) O(R2R)
  • 空间复杂度: O ( n 2 + R ⋅ 2 R ) \mathcal{O}(n^2 + R \cdot 2^R) O(n2+R2R)
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值