售货员的难题(状态压缩动态规划)

此文出处:http://www.cnblogs.com/wdvxdr/p/7253593.html,此乃左神所写博客文章,特此转载学习,感谢!

题目描述

某乡有n个村庄(1<n<20),有一个售货员,他要到各个村庄去售货,各村庄之间的路程s(0<s<1000)是已知的,且A村到B村与B村到A村的路大多不同。为了提高效率,他从商店出发到每个村庄一次,然后返回商店所在的村,假设商店所在的村庄为1,他不知道选择什么样的路线才能使所走的路程最短。请你帮他选择一条最短的路。

写在题解前的话

对于这道题来说,最好的解法应该是状态压缩动态规划,但实际上大部分人写都TLE了两个点,AC的都是DFS+剪枝做的,速度仍比优化过的动态规划快不少,但为什么还要用动态规划做呢,因为做题不是为了AC,而是为了掌握和巩固算法知识。。。

80分题解

用一个二进制数表示当前状态,例如二进制数1011表示第1个,第2个,第4个村庄已经到达过了,然后运用位运算的思想写状态转移方程,这题的状态是f[s][i]表示走过用s表示的村庄后最终到达i点的最优解,那么状态转移方程就是:f[s][i] = min(f[s^(1<<(i-1))][j]+a[j][i],f[s][i]);其中(1<<(i-1)用于表示每一座村庄状压后是哪一个二进制数(如村庄1是1,村庄2是10,村庄3是100。。。)。

对于f[1<<(i-1)|1][i]只经过了第1个,和第i个村庄,自然是等于第1个和第i个村庄的距离了。

复制代码

#include<cstdio>
#include<iostream>
#include<cstring>
#define INF 10000000
using namespace std;
int n,a[22][22],f[1048580][22];

int read()//读入优化
{
    int ans=0;char ch = getchar();
    while(ch<'0'||ch>'9')    ch = getchar();
    while(ch>='0'&&ch<='9') ans= ans*10+ch -'0',ch = getchar();
    return ans; 
}

int main()
{
    n = read();
    int all = (1<<n)-1;
    for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j)
            a[i][j] = read();
    memset(f,127/3,sizeof(f));
    f[0][0]=0;
    for(int i=1;i<=n;++i)
        f[1<<(i-1)|1][i] = a[1][i];//只经过了两个村庄
    for(int s=0;s<=all;++s)//枚举每一种状态
        for(int i=1;i<=n;++i)//枚举最终到达的每一个城市
            if(s&(1<<(i-1)))
                for(int j=1;j<=n;++j)//枚举上一个到达的城市
                    f[s][i] = min(f[s^(1<<(i-1))][j]+a[j][i],f[s][i]);
    int ans = f[(1<<n)-1][2]+a[2][1];
    for(int i=2;i<=n;i++)
        ans = min(ans,f[all][i]+a[i][1]);//寻找最优解
    printf("%d",ans);
    return 0;
}

复制代码

90分题解

80分到90分也没什么优化的,只是不用STL库的min函数,而是用#define(用内联函数优化不了多少)。

100分题解

如果我们注意到的话,不管对于什么状态,第1个村庄一定是经过的,所以我们枚举状态时可以直接用3开始枚举,每次状态+2,这样就可以保证每次状态都一定经过第1个村庄。

当我们枚举枚举每个状态最终到达的城市时,不需要枚举到n,只需枚举到当前状态最高位就可以了。我们可以定义一个k,代表需要枚举城市的个数,p用来代表2的k次方,只用当当前状态大于p时,我们才让k++,并让p乘2.

复制代码

#include<cstdio>
#include<iostream>
#include<cstring>
#define INF 10000000
#define min(a , b) ((a) < (b) ? (a) : (b))
#define lowbit(x) x&-x
using namespace std;
int n,a[22][22],f[1048580][22];

int read()
{
    int ans=0;char ch = getchar();
    while(ch<'0'||ch>'9')    ch = getchar();
    while(ch>='0'&&ch<='9') ans= ans*10+ch -'0',ch = getchar();
    return ans; 
}

int main()
{
    n = read();
    int all = (1<<n)-1;
    for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j)
            a[i][j] = read();
    memset(f,127/3,sizeof(f));
    f[0][0]=0;
    for(int i=1;i<=n;++i)
        f[1<<(i-1)|1][i] = a[1][i];
    for(int s=3,p=4,k=2;s<=all;s+=2)
    {
        if(s > p)
            p = p << 1 , k++;//记录需要枚举城市的个数
        for(int i=1;i<=k;i++)//只需枚举到k,无需枚举到n
            if(s&(1<<(i-1)))
            { 
                int r = s^(1<<(i-1)); 
                for(int j=1;j<=k;j++)//只需枚举到k,无需枚举到n
                    f[s][i] = min(f[r][j]+a[j][i],f[s][i]);
            } 
    }
    int ans = f[(1<<n)-1][2]+a[2][1];
    for(int i=2;i<=n;i++)
        ans = min(ans,f[all][i]+a[i][1]);
    printf("%d",ans);
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
旅行售货员问题(Traveling Salesman Problem,TSP)是一个经典的组合优化问题,其目标是在给定的一些点和这些点之间的距离时,找到一条最短的路径,使得路径经过每个点恰好一次,最后回到起点。 动态规划法(Dynamic Programming,DP)是一种常用的求解TSP问题的方法。具体步骤如下: 1.定义状态:设dp[i][j]表示当前已经经过了集合{i}中的点,并且当前在点j的情况下,从起点出发经过集合{i}中所有点,最终回到起点的最短路径长度。 2.初始化:当集合{i}中只有一个点时,dp[{i},{j}]的值为0。 3.状态转移:对于任意的集合{i},假设i中的最后一个点为k,则dp[i][j]的值等于dp[i- {k}][m] + dist[m][k]的最小值,其中dist[m][k]表示点m和点k之间的距离。 4.最终答案:最终的答案为dp[{0,1,2,...,n-1}][0],即从起点0出发,经过所有点之后,最终回到起点的最短路径长度。 下面是一个简单的Python实现: ``` def tsp_dp(dist): n = len(dist) C = {} for k in range(1, n): C[(1 << k, k)] = (dist[0][k], 0) for subset_size in range(2, n): for subset in itertools.combinations(range(1, n), subset_size): bits = 0 for bit in subset: bits |= 1 << bit for k in subset: prev = bits & ~(1 << k) res = [] for m in subset: if m == 0 or m == k: continue res.append((C[(prev, m)][0] + dist[m][k], m)) C[(bits, k)] = min(res) bits = (2**n - 1) - 1 res = [] for k in range(1, n): res.append((C[(bits, k)][0] + dist[k][0], k)) opt, parent = min(res) path = [0] * n path[0] = 0 path[n-1] = parent for i in range(n-2, 0, -1): path[i] = parent new_bits = bits & ~(1 << parent) _, parent = C[(bits, parent)] bits = new_bits return opt, path ``` 其中,dist是一个n x n的距离矩阵,表示点之间的距离。函数返回最短路径长度opt和路径path。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值