状态压缩 最短Hamilton距离 吃奶酪
最短Hamilton距离
题目描述
给定一张 n n n 个点的带权无向图,点从 0 ∼ n − 1 0\sim n-1 0∼n−1 标号,求起点 0 0 0 到终点 n − 1 n-1 n−1 的最短Hamilton路径。 Hamilton路径的定义是从 0 0 0 到 n − 1 n-1 n−1 不重不漏地经过每个点恰好一次。
分析
一种很容易想到的朴素算法是枚举 0 ∼ n − 1 0\sim n-1 0∼n−1 的排列,排列的先后顺序代表遍历的先后顺序,然后分别求各种顺序下的总路径长度,取最小。但这种方法复杂度为 O ( n ∗ n ! ) O(n*n!) O(n∗n!),实在令人难以接受。
我们可以思考一下该问题解空间的状态,显然,状态仅与当前走过的点和最后走到的点有关,所以我们可以考虑使用 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 0≤k<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;
}