状态压缩 最短Hamilton距离 吃奶酪

状态压缩 最短Hamilton距离 吃奶酪

最短Hamilton距离

题目描述

给定一张 n n n 个点的带权无向图,点从 0 ∼ n − 1 0\sim n-1 0n1 标号,求起点 0 0 0 到终点 n − 1 n-1 n1 的最短Hamilton路径。 Hamilton路径的定义是从 0 0 0 n − 1 n-1 n1 不重不漏地经过每个点恰好一次。

传送门

分析

一种很容易想到的朴素算法是枚举 0 ∼ n − 1 0\sim n-1 0n1 的排列,排列的先后顺序代表遍历的先后顺序,然后分别求各种顺序下的总路径长度,取最小。但这种方法复杂度为 O ( n ∗ n ! ) O(n*n!) O(nn!),实在令人难以接受。

我们可以思考一下该问题解空间的状态,显然,状态仅与当前走过的点和最后走到的点有关,所以我们可以考虑使用 DP 求解此问题。而当前走过的点即指所有点是否走过的状态集,对其中的每个点,只有走过和没走过两种情况,我们可以想到用 1 1 1 表示走过, 0 0 0 表示没走过,进一步可以想到将所有的点的状态用一个二进制数表示,即使用状态压缩,这样我们就确定了本题采用状压 DP 求解的大致思路。

对于具体的状态转移方程,显然,若将点状态集为二进制数 i i i 和最后走到 j j j 点的状态表示为 d p ( i , j ) dp(i,j) dp(i,j),那么有:

d p ( i , j ) = min ⁡ { d p ( i ⊕ ( 1 < < j ) , k ) + G [ k ] [ j ] } dp(i,j)=\min \{dp(i\oplus (1<<j),k)+G[k][j]\} dp(i,j)=min{dp(i(1<<j),k)+G[k][j]},其中 0 ≤ k < n 0\leq k<n 0k<n ( i > > j ) & 1 = 1 (i>>j)\&1=1 (i>>j)&1=1

意思就是说,我们对每一个状态码 i i i 枚举它走过的点作为最后一次走的点 j j j,那么我们通过异或将其置为没走过,再枚举新码走过的点 k k k ,即可用从 k k k 走到 j j j 的值更新 d p ( i , j ) dp(i,j) dp(i,j).

参考程序

//
// Created by Visors on 2020/8/24.
//
// 题目名:最短Hamilton路径
// 题目来源:TODO
// 题目链接:https://www.acwing.com/problem/content/93/
// 算法:状压DP
// 用途:TODO
// 时间复杂度:O(n^2*2^n)
//

#include <bits/stdc++.h>

using namespace std;

const int oo = 0x3f3f3f3f;

vector<vector<int> > G;
vector<vector<int> > dp;


int hamilton(int n) {
    dp.resize(1 << n, vector<int>(n, oo));
    dp[1][0] = 0;
    for (int i = 1; i < dp.size(); i++)
        for (int j = 0, tmp; j < n; j++) {
            tmp = i ^ (1 << j);
//            cout << (i ^ 1 << j) << ' ' << tmp << endl;
            if (i >> j & 1)
                for (int k = 0; k < n; k++) {
                    if (tmp >> k & 1)
                        dp[i][j] = min(dp[i][j], dp[tmp][k] + G[k][j]);
                }
        }
    return dp[(1 << n) - 1][n - 1];
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    int n;
    cin >> n;
    G.resize(n, vector<int>(n));
    for (auto &v:G)
        for (int &it:v)
            cin >> it;
    cout << hamilton(n) << endl;
    return 0;
}

吃奶酪

题目描述

房间里放着 n n n 块奶酪。一只小老鼠要把它们都吃掉,问至少要跑多少距离?老鼠一开始在 ( 0 , 0 ) (0,0) (0,0) 点处。

传送门

分析

上面的题目可以说是板子题,这道题是学弟在洛谷上面刷到的。整个问题被搬到了二维坐标系上,处理方法比较常规,预处理两点间距离即可转换为图上问题。

参考代码

//
// Created by Visors on 2020/8/24.
//
// 题目名:吃奶酪
// 题目来源:luogu
// 题目链接:https://www.luogu.com.cn/problem/P1433
// 算法:状压dp
// 用途:浮点数下的最短Hamilton距离
// 时间复杂度:O(n^2*2^n)
//

#include <bits/stdc++.h>

using namespace std;

const double oo = 0x3f3f3f3f;

typedef pair<double, double> pdd;

vector<pdd> points;
vector<vector<double> > G;
vector<vector<double> > dp;

inline double dist(const pdd &a, const pdd &b) {
    return sqrt((a.first - b.first) * (a.first - b.first)
                + (a.second - b.second) * (a.second - b.second));
}

inline double Hamilton(int n) {
    int len = 1 << n;
    dp.resize(len, vector<double>(n, oo));
    dp[1][0] = 0.0;
    for (int i = 1; i < len; i++)
        for (int j = 0, tmp; j < n; j++) {
            tmp = i ^ 1 << j;
            if (i >> j & 1)
                for (int k = 0; k < n; k++)
                    if (tmp >> k & 1)
                        dp[i][j] = min(dp[i][j], dp[tmp][k] + G[k][j]);
        }
    double ans = oo;
    for (int i = 1; i < n; i++)
        ans = min(ans, dp[len - 1][i]);
    return ans;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    int n;
    cin >> n;
    points.emplace_back(0.0, 0.0);
    double x, y;
    for (int i = 0; i < n; i++) {
        cin >> x >> y;
        points.emplace_back(x, y);
    }
    G.resize(n + 1, vector<double>(n + 1));
    for (int i = 0; i <= n; i++)
        for (int j = i + 1; j <= n; j++)
            G[i][j] = G[j][i] = dist(points[i], points[j]);
    cout << fixed << setprecision(2) << Hamilton(n + 1) << endl;
    return 0;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值