洛谷 P1171 售货员的难题

题意

给定 \(n\) (\(n\le 20\))个点 , 从 \(1\)号点开始 , 依次到达其余\(n-1\)个点 , 并且回到\(1\)号点的最短路径 , 两点之间往返权值不同

思路层

首先看到\(n\)差不多在\(20-30\)这个范围 , 就可以想到动态规划了

这显然是一个图 ,

为什么不同最短路 ?

如果是一个起点到达图某个点 , 那么可以想最短路算法 ; 如果是经过所有路 , 就不能用了

状压即可

在转移时 , 需要知道当前状态在哪里

所以可以设置三进制状压 , \(0\)为未访问 , \(1\)已经过 , \(2\)为正在

实际写代码可以写成二进制 \(+\) 一维表示当前在哪里 , 因为二进制的位运算等函数都比三进制成熟

代码层

需要输出答案 , 需要有\(f_{sta,cur}\) , 其中\(sta\)\(1<<N\)大小 , \(cur\)\(N\)大小

  1. 初始化
    需要将\(f_{2^{N-1},1}\)设置为\(0\) , 其余节点初始化为\(inf\)

  2. \(dp\)
    考虑一个未更新状态\(sta\)的二进制 , 那么更新该状态需要的都是\(\le sta\)的状态 , 所以优先考虑按照数值递增

另一方面 , 最高位为\(1\)开始比较容易乱 , 可以考虑倒着看二进制 , 即初始状态为\(0000001\) , 然后\(dp\)

优先考虑单调递增的方式进行\(dp\) , 不行了再考虑其他方式进行\(dp\)

代码


#include<bits/stdc++.h>
using namespace std;
#define int long long int
inline int read() {
    int ans = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-')f = -1;
        ch = getchar();
    }
    while (ch <= '9' && ch >= '0') {
        ans = ans * 10 + ch - '0';
        ch = getchar();
    }
    return ans * f;
}
const int N = 22;
int g[N][N];
int f[1<<N][N];
signed main() {
    int n =read();
    for (int i =1;i<=n;i++)
        for (int j =1; j<=n;j++)
            g[i][j]=read();
    for (int i = 1;i<1<<n;i+=2)
        for (int j =1;j<=n;j++)
            f[i][j] = 1e18;
    f[1][1] =0;
    int p = 2;
    int s = 1;
    for (int i =3;i< 1<<n;i+=2) {
        if (i > 1<<p)p++;
        for (int j = 2;j<=p;j++) {
            if (i&1ll<<j-1) {
                s = i ^ 1ll<<j-1;
                for (int l=1;l<=p;l++) {
                    if (l==j || !(s&1ll<<l-1))continue;
                    f[i][j] = min(f[i][j],f[s][l] + g[l][j]);

                }
            }
        }
    }
    int ans = 1e18;
    for (int i = 2; i<= n;i++) {
        ans =min( f[(1<<n)-1][i] + g[i][1],ans);
    }
    cout<<ans;
    return 0;
}
posted @ 2025-04-20 01:33  Guaninf  阅读(10)  评论(0)    收藏  举报