zoukankan      html  css  js  c++  java
  • 最短Hamilton路径

    题目描述

    给定一张 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]≤(10^7)

    输入样例

    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

    思路

    首先想下暴力算法,这里直接给出一个例子。
    比如数据有 5 个点,分别是 0,1,2,3,4
    那么在爆搜的时候,会枚举一下六种路径情况(只算对答案有贡献的情况的话):

    • case 1: 0→1→2→3→4
    • case 2: 0→1→3→2→4
    • case 3: 0→2→1→3→4
    • case 4: 0→2→3→1→4
    • case 5: 0→3→1→2→4
    • case 6: 0→3→2→1→4

    那么观察一下 case 1 和 case 3,可以发现,我们在计算从点 0 到点 3 的路径时,其实并不关心这两中路径经过的点的顺序,而是只需要这两种路径中的较小值,因为只有较小值可能对答案有贡献。
    所以,我们在枚举路径的时候,只需要记录两个属性:当前经过的点集,当前到了哪个点。
    而当前经过的点集不是一个数。观察到数据中点数不会超过 20,我们可以用一个二进制数表示当前经过的点集。其中第 i 位为 1/0 表示是/否经过了点 i。
    然后用闫式 dp 分析法考虑 dp
    状态表示:f[i][j]。其中 i 是一个二进制数,表示点集的方法如上述所示。

    • 集合:经过的点集为 i ,且当前到了点 j 上的所有路径。
    • 属性:路径总长度的最小值。

    状态计算:假设当前要从点 k转移到 j。那么根据 Hamilton 路径的定义,走到点 k 的路径就不能经过点 j,所以就可以推出状态转移方程
    f[i][j] = min{f[i-(1 << j)][k] + weight[k][j]}
    其中weight[k][j]表示从点 k 到点 j 的距离。
    所有状态转移完后,根据 f[i][j] 的定义,要输出 f[111⋯11((n−1)个1)][n−1]。
    那么怎么构造 n - 1 个 1 呢,可以直接通过 1 << n 求出 100⋯0((n−1)个0),然后减一即可。

    代码

    #include<iostream>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int N=20,M=1<<20;//一共有2的20次方种情况
    int f[M][N],weight[N][N];
    int main()
    {
        ios::sync_with_stdio(false);
        cin.tie(0);
        int n;
        cin>>n;
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<n;j++)
            {
                cin>>weight[i][j];//输入点i与点j之间的距离
            }
        }
        memset(f,0x3f,sizeof(f));//由于要求最小值,所以这里将 f 初始化为正无穷会更好处理一些(注意:memset函数是按照字节进行赋值的)
        f[1][0]=0;//由于题目要求0点为起始点,所以0 -> 0的距离长度为0。对其初始化后方便接下来递推的过程。
        for(int i=1;i<(1<<n);i++)
        {
            for(int j=0;j<n;j++)//枚举当前所在的点
            {
                if(i>>j&1)//判断路径i中是否包括当前点j,如果包括当前点,则可以进行状态转移(注意: >>运算符的优先级高于&运算符的优先级,所以(i>>j)&1可以写成i>>j&1)
                {
                    //要完成点k到点j的转移,所以要来枚举k。
                    for(int k=0;k<n;k++)
                    {
                        if(i-(1<<j)>>k&1)//只有去除掉点j后路线中仍然包含点k才能说明路线是在点k的基础上向点j转移的(注意:算术运算符的优先级大于位运算符的优先级)
                        {
                            f[i][j]=min(f[i-(1<<j)][k]+weight[k][j],f[i][j]);
                        }
                    }
                }
            }
        }
        cout<<f[(1<<n)-1][n-1]<<endl;//由于题目要求计算从点n到点n-1的路径长度,所以(1<<n)-1的二进制形式为111...111[共有(n-1)个1].
        /*
           f[i][j]中
           i的含义是当前的路径(eg:5的二进制形式为101,表示路径经过点0和点2,不经过点1)
           j的含义是当前路径的终点
        */
        return 0;
    }
    
  • 相关阅读:
    bootstrap轮播图--兼容IE7
    css清除默认样式和设置公共样式
    jq三级全选全不选
    妙味课堂--导航手风琴效果(转)
    妙味课堂首页--鼠标经过文字实现类似琴效果(转)
    eclipse导入git项目(转)
    根据屏幕分辨率设置样式
    css选择器
    h5新增标签兼容性
    4.Reverse Words in a String-Leetcode
  • 原文地址:https://www.cnblogs.com/Acapplella/p/13544964.html
Copyright © 2011-2022 走看看