洛谷P1171 售货员的难题 题解 & 旅行商问题(TSP 问题)模板

题目链接:P1171 售货员的难题

题目描述:

某乡有 \(n\) 个村庄,有一个旅行商 qry,他要到各个村庄去售货。

各村庄之间的路程 \(e(i,j)\) 是已知的,且 \(A\) 村到 \(B\) 村与 \(B\) 村到 \(A\) 村的路大多不同(有向图)。

为了提高效率,qry 从商店出发到每个村庄一次,然后返回商店所在的村。

假设商店所在的村庄为 \(1\),他不知道选择什么样的路线才能使所走的路程最短。

请你帮他选择一条最短的路。

输入格式:

第一行是一个整数,表示村庄数 \(n\)。

接下来 \(n\) 行,每行 \(n\) 个整数,第 \(i\) 行的第 \(j\) 个整数表示 \(i\) 到 \(j\) 的单向路径的距离 \(e(i,j)\)。

输出格式:

一行一个整数表示最短的路程。

数据范围:

保证 \(2 \leq n \leq 20,~1 \leq e(i,j) < 10^3,~e(i,i) = 0\)。

考虑状压dp。方便起见,把节点记为 \(0\) 到 \(n-1\) ,而不是 \(1\) 到 \(n\) 。

记 \(f(S,i)\) 为当前走到 \(i\) ,已经走过了 \(S\) 集合中的点时的最小哈密顿路径长。

这里最小哈密顿路径 就是经过每个节点恰好一次的、总长最短的路径。

初始值 \(f(\{0\},0)=0\) ,即从 \(0\) 出发走过了 \(0\) 的花费,当然为 \(0\) 。

注意旅行商还要回来,答案就是 \(\displaystyle \min_{0 \le i \le n - 1} (f(U,i)+ e(i,0))\) ,其中 \(U\) 为全集。

考虑转移,每轮枚举 \(i\,(i \in S)\) ,有 \[ f(S,i) \gets \min_{(j \ne i) \land (j \in S)}\left(f(S,i), \,f(S\setminus\{i\},j) + e(j,i)\right) \] 直接转移即可,时间复杂度 \(O(n^2\cdot2^n)\)

代码:(循环rep(S, 2, U) 内,注释部分的写法常数较大,没注释的常数较小不过略难理解)

#include <bits/stdc++.h>
using namespace std;
// #define int long long
#define INF 0x3f3f3f3f3f3f3f3f
typedef long long ll;
template<typename T> void up(T &x, T y) { x < y ? x = y : 0; }
template<typename T> void down(T &x, T y) { x > y ? x = y : 0; }
#define rep(i, a, b) for(int i = (a), i##END = (b); i <= i##END; i++)
#define Rep(i, a, b) for(int i = (a), i##END = (b); i >= i##END; i--)
#define N ((int)(1 << 20) + 5)

int n, e[N][22]; ll f[N][22];
int lowbit(int x) { return x & (-x); }
// 定义的 ctz(x) 当 x > 0 返回 x 右侧 0 的个数,比如 ctz(8) = 3 ,因为 8 = (1000)_2
#define ctz(x) __builtin_ctz(x)
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    cin >> n; const int U = (1 << n) - 1;
    rep(i, 0, n - 1) rep(j, 0, n - 1) cin >> e[i][j];
    memset(f, 0x3f, sizeof(f)); f[1][0] = 0;
    rep(S, 2, U)
    {   
        // rep(i, 0, n - 1) if((S >> i) & 1)
        //     rep(j, 0, n - 1) if(j != i && ((S >> j) & 1))
        //         down(f[S][i], f[S ^ (1 << i)][j] + e[j][i]);
        for(int x = S; x > 0; x -= lowbit(x))
            for(int y = S; y > 0; y -= lowbit(y))
            {
                const int i = ctz(x), j = ctz(y);
                if(i != j) down(f[S][i], f[S ^ (1 << i)][j] + e[j][i]);
            }
    }
    ll res = INF;
    rep(i, 0, n - 1) down(res, f[U][i] + e[i][0]);
    cout << res << '\n';
    return 0;
}