【AcWing 91】 最短Hamilton路径 状压DP + 位运算 详解

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

输入格式
第一行输入整数n。

接下来n行每行n个整数,其中第i行第j个整数表示点i到j的距离(记为a[i,j])。

对于任意的x,y,z,数据保证 a[x,x]=0,a[x,y]=a[y,x] 并且 a[x,y]+a[y,z]>=a[x,z]。

输出格式
输出一个整数,表示最短Hamilton路径的长度。

数据范围
1≤n≤20
0≤a[i,j]≤107
输入样例:
5
0 2 4 5 1
2 0 6 5 3
4 6 0 8 3
5 5 8 0 5
1 3 3 5 0
输出样例:
18

题意:如题

思路:

一道NP完全问题。别看数据量小,暴力的话,时间复杂度是O(n * n!),光是一个20的阶乘就已经到1018了!!!
毫无疑问,这道题要借助算法的思想尽量地优化。首先我们发现,我们并不需要知道这个方案是如何选择的(顺序),而是只需要解得最优解即可。所以,我们无非要解决两个问题
1.当前选了哪些点?
2.对这些选的点进行路径优选
第一个,我们有20个点,如果用数组下标表示每个点的选择情况的话,那得开220维度的数组(a[1][0][0][1]…[1],20维,每维两层),1表示选,0表示不选。220的大小,虽说开得到这么大的数组,但是未免过于麻烦(不会有人能开着20维数组写代码吧?不会吧?)。但是注意到,我们的情况就是一个01问题——选与不选。这就对应到了二进制。比如前面说的这个做例子a[1][1][0][1],我们开了前四维,表示的是1101,就是选了3 2 0位置的意思(这三个位置上为1),但是,我们似乎不用这么麻烦。直接a[1101]不就行了吗?数据量上限仍然是220,那我们就开一个这么大的一维数组,下标用来存对应的二进制数,然后要第j个点选择,就直接将下标右移j位,这时候最右边的就是你要的第j个(通过&1判断是否为1)。
解决了第一个问题,也就是存储的问题。也就是用a[i]表示i集合,这个i集合存储的是一个二进制信息,代表我选了哪几个点。然后我们现在要对每个选了的点进行状态转移。那就需要多开一维。a[i][j],来表示我i集合中对第j位进行优选。
那么怎么对a[i][j]进行状态转移呢?我们无非就是要找一个中间状态k,看看k->j的花费加上原来选到k而没有j的花费会不会比我当前的选择少。也就是,如果我k这个位置的点已经在集合里面的话,就看看k- 这部分转化成代码就是

if (i >> k & 1)       //你要从k状态转移的前提是k这个位置的点有选择
        f[i][j]=min(f[i][j],f[i^(1<<j)][k]+weight[k][j]);   //就看看当前选择和去掉j这个点,让状态从k到j的选择哪个好

i>>k&1 判断i集合中k这个位置是否为1(这个点有没有被选择)
i^(1< 然后f[i^(1<j这条边长的总花费(来和原来的f[i][j]比较)

核心思路就是这样,明白了之后写代码就很轻松了。最后返回的结果即是

f[(1<

表示n个位置全部选好,并且状态停在n-1位上的最优解

若还有不理解详见代码注释:

#include 
using namespace std;
int n,f[1<<20][21],i,j,k;
int weight[21][21];
int main()
{
    ios::sync_with_stdio(false);
    memset(f,0x3f,sizeof(f));//初始化最大值
    cin>>n;
    for (i=0; i<n; i++)
        for (j=0; j<n; j++)
            cin>>weight[i][j];
    f[1][0]=0;//第一个点是不需要任何费用的
    for (i=1; i<(1<<n); i++)    //i集合,用来存储点的选择情况,如 1 1 0 1 ,代表选了0 2 3点
        for (j=0; j<n; j++)            //枚举当前停靠在哪一位上,对这个位进行状态选择
            if ((i>>j & 1))         //如果j这个位置的点被选择了的话
                for (k=0; k<n; k++)         //看看可不可以从k状态转移过来,即是看看当前的选择优还是从k转移到j的选择优
                    if(j==k) continue;      //同位跳过
                     else if (i >> k & 1)       //你要从k状态转移的前提是k这个位置的点有选择
                        f[i][j]=min(f[i][j],f[i^(1<<j)][k]+weight[k][j]);   //就看看当前选择和去掉j这个点,让状态从k到j的选择哪个好
    cout<<f[(1<<n)-1][n-1];     //最后的(1<
    return 0;
}


你可能感兴趣的:(AcWing,位运算,动态规划)