为了更好的阅读体检,可以查看我的算法学习网站
在线评测链接:P1165
题目内容
塔子哥是一名旅游爱好者,他热爱到处游玩,这个寒假也不例外。
他计划前往 n n n个城市游玩,这些城市都有着独特的风景和文化。他想要尽可能地省钱,但又不想错过每座城市的精华。为此,他收集了这些城市之间的直达交通费用信息,并打算以最低的花费游览这些城市, n n n 个城市间的直达交通费由一个二维矩阵表示。
他决定从城市 0 0 0 开始出发,然后依次游览其余 n − 1 n-1 n−1 座城市,最后回到城市 0 0 0。他可以在同一个城市多次停留,以便更好地体验当地的文化和风景。
他非常希望在寒假期间充分利用自己的时间和预算,因此他想要计算出以最低的花费游览所有城市的总费用。
他希望能够为自己的旅游行程做出最佳的规划,让自己的旅游经历充满回忆和意义。
输入描述
第一行一个整数 n n n( 1 ≤ n ≤ 15 1 \leq n \leq 15 1≤n≤15)
接下来 n n n行,每行 n n n个整数。代表邻接矩阵, ( 1 ≤ a ∗ i , j ≤ 1000 ) (1 \leq a*_{i,j} \leq 1000) (1≤a∗i,j≤1000) , a ∗ i , i = 0 a_*{i,i} = 0 a∗i,i=0
输出描述
输出一个整数,代表最低花费
样例
输入
4
0 3 1 2
4 0 2 1
4 1 0 1
2 1 4 0
输出
5
思路
Floyd最短路 + 状压dp
这个题目要拿满分并不简单。具有一定的挑战性。基础较差的同学酌情入坑学习!
状压dp入门四部曲
1.先搞懂基本的动态规划
2.认识状压 d p dp dp:信息学奥赛之动态规划《状态压缩DP》
3.认识 T S P TSP TSP:算法竞赛入门经典第二版(紫书) 复杂动态规划 章节 关于 T S P TSP TSP的讨论
如果没有纸质书的同学,可以直接看csdn讲解 TSP问题-状压dp 。 另外,如果只是为了搞懂这道题,到这就行了
4.426 状态压缩DP 玉米田【动态规划】 以及同 U P UP UP主的其他状压 d p dp dp例题讲解视频
先让我们考虑一个简化版本
给你一张完全图(任意两个点之间有边)。求解一条起点是 0 0 0的路径使得其访问过所有点恰好一次并且最终要回到 0 0 0点。 n ≤ 15 n \leq 15 n≤15
那么这是经典的 T S P TSP TSP问题。直接上状压 d p dp dp即可。
本题:TSP问题的变化版
变化在于每个点允许重复访问。那考虑我们要从 u u u走到 v v v,允许重复访问就意味着我们可以经过一些已经访问的点来缩短从 u → v u \rightarrow v u→v 的花费。所以不难想到直接走 u u u到 v v v之间的最短路即可,所以我们可以
1.使用Floyd算法 预处理出任意两个点之间的最短路。
2.在 T S P TSP TSP的过程中,两点之间的移动的花费为两点之间的最短路。
Q Q Q:这样做是否会和 d p dp dp的定义有冲突?因为在走最短路的过程中,有些路径上的点已经被访问过了,但是我们的 d p dp dp并没有更新他们?
没有冲突。因为我们 d p dp dp定义的访问或没访问,是人为的定序,是为了保证能够访问过所有可能情况。而不是看它实际被访问过没。
类似题目推荐
LeetCode
CodeFun2000
P1188 2023.04.12-微众银行春招-第三题-魔法收纳器
笔试真题几乎没考过状态压缩 d p dp dp这么难的知识点。这里推荐一个状态压缩相关的比较有意思的题。
代码
CPP
#include <bits/stdc++.h>
using namespace std;
const int maxn = 16;
int dp[maxn][1 << maxn];
int d[maxn][maxn];
int main() {
int n;
cin >> n;
for (int i = 0 ; i < n ; i++){
for (int j = 0 ; j < n ; j++){
cin >> d[i][j];
}
}
// 根据邻接矩阵跑Floyd算法,之后的d[i][j]就代表i->j的最短路
for (int k = 0 ; k < n ; k++){
for (int i = 0 ; i < n ; i++){
for (int j = 0 ; j < n ; j++){
d[i][j] = min(d[i][j] , d[i][k] + d[k][j]);
}
}
}
// 状态压缩dp解决tsp问题
int s = 1 << n;
for (int i = 0 ; i <= n ; i++){
for (int j = 0 ; j <= s ; j++){
dp[i][j] = 1e9;
}
}
//dp[i][j]的含义是:现在站在i节点并且已经访问的节点集合是j这个状态下的最小花费
dp[0][1] = 0;
// 枚举节点集合状态i
for (int i = 1 ; i < s ; i++){
// 枚举现在站在的节点j
for (int j = 0 ; j < n ; j++){
// j必须要属于i才行
if (((i >> j) & 1) == 0)
continue;
// 获得前驱状态last
int last = i ^ (1 << j);
// 枚举上一个站在的节点k
for (int k = 0 ; k < n ; k++){
if (((last >> k) & 1) == 0)
continue;
if (dp[k][last] == 1e9)
continue;
// dp转移,从k走到j
dp[j][i] = min(dp[j][i] , dp[k][last] + d[k][j]);
}
}
}
int ans = 1e9;
// 由于还要回到0,所以枚举一下所有结束点到0的花费,求最小值
for (int i = 1 ; i < n ; i++)
ans = min(ans , dp[i][s - 1] + d[i][0]);
// 输出答案
cout << ans << endl;
return 0;
}
python
import sys
n = int(input())
d = []
for _ in range(n):
line = list(map(int, input().split(" ")))
d.append(line)
# floyd
for k in range(n):
for i in range(n):
for j in range(n):
d[i][j] = min(d[i][j], d[i][k] + d[k][j])
dp = []
s = 1 << n
for _ in range(n+1):
dp.append([sys.maxsize for _ in range(s+1)])
dp[0][1] = 0
# 站在i节点并且已经访问的节点集合是j这个状态下的最小花费
for i in range(1, s, 1):
for j in range(n):
if (i >> j) & 1 == 0:
continue
last = i ^ (1 << j)
for k in range(n):
if (last >> k) & 1 == 0:
continue
if dp[k][last] == sys.maxsize:
continue
dp[j][i] = min(dp[j][i], dp[k][last] + d[k][j])
ans = sys.maxsize
for i in range(1, n, 1):
ans = min(ans, dp[i][s - 1] + d[i][0])
print(ans)
Java
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[][] graph = new int[n][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
graph[i][j] = sc.nextInt();
}
}
int res = min(graph);
System.out.println(res);
}
public static int min(int[][] edges) {
int n = edges.length;
// floyd
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
for (int k = 0; k < n; k++) {
if (i != k && k != j) {
edges[i][j] = Math.min(edges[i][j], edges[i][k] + edges[k][j]);
}
}
}
}
// TSP
int[][] dp = new int[1 << n][n];
for (int i = 0; i < 1 << n; i++) {
Arrays.fill(dp[i], Integer.MAX_VALUE / 2);
}
dp[1][0] = 0;
for (int i = 1; i < 1 << n; i++) {
for (int j = 0; j < n; j++) {
if ((i >> j & 1) == 1) {
for (int k = 0; k < n; k++) {
if (j != k && (i >> k & 1) == 1) {
int p = i - (1 << k);
dp[i][k] = Math.min(dp[i][k], dp[p][j] + edges[j][k]);
}
}
}
}
}
int res = Integer.MAX_VALUE / 2;
for (int i = 1; i < n; i++) {
res = Math.min(res, dp[(1 << n) - 1][i] + edges[i][0]);
}
return res;
}
}
// by kw2012
Go
package main
import (
"fmt"
"math"
)
func main() {
var n int
fmt.Scan(&n)
d := make([][]int, n)
for i := range d {
d[i] = make([]int, n)
for j := range d[i] {
fmt.Scan(&d[i][j])
}
}
// floyd
for k := 0; k < n; k++ {
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
d[i][j] = int(math.Min(float64(d[i][j]), float64(d[i][k]+d[k][j])))
}
}
}
// tsp
s := (1 << n)
dp := make([][]int, n+1)
for i := range dp {
dp[i] = make([]int, s+1)
for j := range dp[i] {
dp[i][j] = math.MaxInt32
}
}
dp[0][1] = 0
for i := 1; i <= s; i++ {
for j := 0; j < n; j++ {
if ((i >> j) & 1) == 0 {
continue
}
last := i ^ (1 << j)
for k := 0; k < n; k++ {
if ((last >> k) & 1) == 0 {
continue
}
if dp[k][last] == math.MaxInt32 {
continue
}
dp[j][i] = int(math.Min(float64(dp[j][i]), float64(dp[k][last]+d[k][j])))
}
}
}
// 回到起点
ans := math.MaxInt32
for i := 1; i < n; i++ {
ans = int(math.Min(float64(ans), float64(dp[i][s-1]+d[i][0])))
}
fmt.Println(ans)
}
Js
let input = '';
process.stdin.on('data', (data) => {
input += data;
return;
});
process.stdin.on('end', () => {
input = input.split('\n');
const n = parseInt(input.shift());
const d = [];
for(let i=0; i<n; i++) {
const line = input.shift().split(" ").map((e)=>parseInt(e));
d.push(line);
}
// 对读入进来的邻接矩阵进行floyd
for(let k = 0; k<n; k++) {
for(let i = 0; i<n; i++) {
for(let j = 0; j<n; j++) {
d[i][j] = Math.min(d[i][j], d[i][k] + d[k][j]);
}
}
}
// TSP过程
const s = 1 << n;
const dp = new Array(n+1).fill(0).map(()=>{
return new Array(s+1).fill(Number.MAX_SAFE_INTEGER);
});
dp[0][1] = 0;
for(let i=1; i<s; i++) {
for(let j=0; j<n; j++) {
if(((i>>j) &1)==0) {
continue;
}
const last = i^(1<<j);
for(let k=0; k<n; k++) {
if(!((last>>k)&1)) {
continue;
}
if(dp[k][last]===Number.MAX_SAFE_INTEGER) {
continue;
}
dp[j][i] = Math.min(dp[j][i], dp[k][last]+d[k][j]);
}
}
}
let ans = Number.MAX_SAFE_INTEGER;
for(let i=1; i<n; i++) {
ans = Math.min(ans, dp[i][s-1]+d[i][0]);
}
console.log(ans);
});
题目内容均收集自互联网,如如若此项内容侵犯了原著者的合法权益,可联系我: (CSDN网站注册用户名: 塔子哥学算法) 进行删除。