【备战秋招】每日一题:2022年9月23日-华为OD机试(第三题)-最省出游方案

该文介绍了一个旅行者计划问题,涉及到城市间最短路径的寻找。通过Floyd算法预处理最短路径,然后用状压dp解决旅行者问题,找到以最低花费游览所有城市的总费用。文章提供了多种编程语言的代码示例,并提到了这个问题的简化版本和相关知识点。
摘要由CSDN通过智能技术生成

为了更好的阅读体检,可以查看我的算法学习网站
在线评测链接:P1165

题目内容

塔子哥是一名旅游爱好者,他热爱到处游玩,这个寒假也不例外。

他计划前往 n n n个城市游玩,这些城市都有着独特的风景和文化。他想要尽可能地省钱,但又不想错过每座城市的精华。为此,他收集了这些城市之间的直达交通费用信息,并打算以最低的花费游览这些城市, n n n 个城市间的直达交通费由一个二维矩阵表示。

他决定从城市 0 0 0 开始出发,然后依次游览其余 n − 1 n-1 n1 座城市,最后回到城市 0 0 0。他可以在同一个城市多次停留,以便更好地体验当地的文化和风景。

他非常希望在寒假期间充分利用自己的时间和预算,因此他想要计算出以最低的花费游览所有城市的总费用。

他希望能够为自己的旅游行程做出最佳的规划,让自己的旅游经历充满回忆和意义。

输入描述

第一行一个整数 n n n( 1 ≤ n ≤ 15 1 \leq n \leq 15 1n15)

接下来 n n n行,每行 n n n个整数。代表邻接矩阵, ( 1 ≤ a ∗ i , j ≤ 1000 ) (1 \leq a*_{i,j} \leq 1000) (1ai,j1000) , a ∗ i , i = 0 a_*{i,i} = 0 ai,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 n15

那么这是经典的 T S P TSP TSP问题。直接上状压 d p dp dp即可。

本题:TSP问题的变化版

变化在于每个点允许重复访问。那考虑我们要从 u u u走到 v v v,允许重复访问就意味着我们可以经过一些已经访问的点来缩短从 u → v u \rightarrow v uv 的花费。所以不难想到直接走 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

1349. 参加考试的最大学生数

1994. 好子集的数目

1494. 并行课程 II

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网站注册用户名: 塔子哥学算法) 进行删除。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

塔子哥学算法

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值