题目## 题目
解题思路
这是一个动态规划问题,但需要从终点往起点推导:
- 创建一个 d p dp dp 数组, d p [ i ] [ j ] dp[i][j] dp[i][j] 表示到达位置 ( i , j ) (i,j) (i,j) 时需要的最小血量
- 从右下角开始,逆向推导到左上角
- 对于每个位置
(
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,1−map[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])
- 最终 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 数组
解题思路
-
核心思想:
- 使用状态压缩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 表示的点集合时的最小花费
- 通过二进制表示已访问的点集合
-
状态转移:
- 对于当前状态 ( 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+R⋅2R)
- Floyd算法: O ( n 3 ) O(n^3) O(n3)
- 状态压缩DP: O ( R ⋅ 2 R ) O(R \cdot 2^R) O(R⋅2R)
- 空间复杂度: O ( n 2 + R ⋅ 2 R ) \mathcal{O}(n^2 + R \cdot 2^R) O(n2+R⋅2R)
267

被折叠的 条评论
为什么被折叠?



