zoukankan      html  css  js  c++  java
  • 旅行商问题——状态压缩DP

    问题简介

    有n个城市,每个城市间均有道路,一个推销员要从某个城市出发,到其余的n-1个城市一次且仅且一次,然后回到再回到出发点。问销售员应如何经过这些城市是他所走的路线最短?

    用图论的语言描述就是:给定一个权值为正数的赋权完全图,求各边权值和最小的哈密尔顿回路。

    这个问题就是著名的旅行商问题(TSP,Traveling Salesman Problem),TSP问题是NP问题,没有已知的多项式时间的高效算法可以解决之一问题。问题在1930年首次被形式化,并且是在最优化中研究最深入的问题之一,许多优化方法都用它作为一个基准,例如动态规划,最邻近法,插入法,模拟退火算法,遗传算法,神经网络算法等。

    经典应用实例

    印制电路板转孔

    在一块电路板上打成百上千个孔,转头在这些孔之间移动,要求移动的距离之和最小。把这个问题转化为TSP,孔相当于城市.孔到孔之问的移动时间就是距离,转头的移动距离之和就是一次巡回的距离

    生产安排

    假设要在同一组机器上制造n种不同的产品,生产是周期性进行的,即在每一个生产周期这n种产品都要被制造。要生产这些产品有两种开销,一种是制造第i种产品时所耗费的资金(1≤i≤n),称为生产成本,另一种是这些机器由制造第i种产品变到制造第j种产品时所耗费的开支cij称为转换成本。显然,生产成本与生产顺序无关。于是,我们希望找到一种制造这些产品的顺序,使得制造这n种产品的转换成本和为最小。由于生产是周期进行的,因此在开始下一周期生产时也要开支转换成本,它等于由最后一种产品变到制造第一种产品的转换成本。于是,可以把这个问题看成是一个具有n个结点,边成本为cij图的货郎担问题。

    代码实现

    给定一个n个顶点组成的带权有向图的距离矩阵d(i,j)(INF表示没有边)。要求从顶点0出发,经过每个顶点恰好一次后再回到顶点0.问所经过的边的总权重的最少值是多少?(2≤n≤15),(0≤d(i,j)≤1000).

    分析:

    虽然TSP是NP困难的,不过在程序设计竞赛中还是有可能出现这种范围较小的题目。

    所有可能的路线共有(n-1)! 种,这是一个非常大的值,即使在本题n已经很小,仍无法试遍每一种情况。对于这个问题,我们可以用DP来解决。

    假设现在已经访问过的顶点的集合(起点0当作还未访问过的顶点)为S,当前所在的顶点为v,用d[S][v]表示从顶点v出发访问剩余的所有顶点,最终回到顶点0的路径的权重之和最小的路径。

    已经访问过的集合S如何表示?由于S是有基数最大为n的集合,所以可以用n为二进制来表示集合,把每一个元素的选取与否对应到一个二进制位中,从而状态压缩成一个整数,像这种针对集合的DP我们一般叫做状态压缩DP

    时间复杂度O(2nn2)(2n * n种状态,每种状态有n种选择)。

    代码:

     1 #include<stdio.h>
     2 #include<cstring>
     3 #include<algorithm>
     4 using namespace std;
     5 
     6 const int INF = 0x3f3f3f3f;
     7 const int maxn = 15;
     8 int n,m,G[maxn][maxn];
     9 int d[1 << maxn][maxn];    //记忆化搜索使用的数组
    10 
    11 //已经访问过的节点集合为S,当前位置为v
    12 //用d[S][v]从v出发访问剩余的所有顶点,最终回到顶点0的路径的权重总和的最小值
    13 int dp(int S, int v)
    14 {
    15     int& ans = d[S][v];
    16     if (ans >= 0)  return  ans;            //已经求出来了,直接返回
    17     if (S == (1 << n) - 1 && v == 0)  return ans = 0;    //已经访问过所有节点并回到0号点
    18     ans = INF;
    19     for (int u = 0; u < n; u++)  if ((!((S >> u) & 1)) && G[v][u])
    20         ans = min(ans, dp(S | (1 << u), u) + G[v][u]);
    21     return ans;
    22 }
    23 
    24 void solve()
    25 {
    26     //memset(G, INF, sizeof(G));
    27     memset(d, -1, sizeof(d));
    28     printf("%d
    ", dp(0, 0));
    29 }
    30 int main()
    31 {
    32     int T;
    33     scanf("%d", &T);
    34     while (T--)
    35     {
    36         scanf("%d%d", &n,&m);
    37         for (int i = 0; i < m; i++)
    38         {
    39             int u, v,w;
    40             scanf("%d%d%d", &u, &v,&w);
    41             G[u][v] = w;
    42         }
    43         solve();
    44     }
    45     return 0;
    46 }

     对于不是整数的情况,很多时候很难确定一个适合的递推顺序,因此使用记忆化搜索可以避免这个问题。不过在这个问题中,对于任意两个整数i,j,如果它们对应的集合满足S(i) ⊆ S(j),就有i ≤ j。所以这题还可以用循环求出答案。

     1 #include<stdio.h>
     2 #include<cstring>
     3 #include<algorithm>
     4 using namespace std;
     5 
     6 const int INF = 0x3f3f3f3f;
     7 const int maxn = 15;
     8 int n, m, G[maxn][maxn];
     9 int d[1 << maxn][maxn];    //记忆化搜索使用的数组
    10 
    11 void solve()
    12 {
    13     //用足够大的值初始化数组
    14     for (int S = 0; S < (1 << n); S++)
    15         fill(d[S], d[S] + n, INF);
    16     d[(1 << n) - 1][0] = 0;
    17     
    18     //根据递推式依次计算
    19     for (int S = (1 << n) - 2; S >= 0; S--)
    20         for (int v = 0; v < n; v++)
    21             for (int u = 0; u < n; u++)
    22                 if ((!(S >> u & 1)) && G[v][u])
    23                     d[S][v] = min(d[S][v], d[S | 1 << u][u] + G[v][u]);
    24     printf("%d
    ", d[0][0]);
    25 }
    26 int main()
    27 {
    28     int T;
    29     scanf("%d", &T);
    30     while (T--)
    31     {
    32         scanf("%d%d", &n, &m);
    33         for (int i = 0; i < m; i++)
    34         {
    35             int u, v, w;
    36             scanf("%d%d%d", &u, &v, &w);
    37             G[u][v] = w;
    38         }
    39         solve();
    40     }
    41     return 0;
    42 }

    参考链接:

    https://baike.baidu.com/item/NP完全问题/4934286?fr=aladdin

    https://baike.baidu.com/item/旅行商问题/7737042?fr=aladdin

    https://baike.baidu.com/item/%E8%B4%A7%E9%83%8E%E6%8B%85%E9%97%AE%E9%A2%98

  • 相关阅读:
    推荐几款极简的手机浏览器
    const关键字到底该怎么用
    让你的代码更加优雅的编程技巧-跳转表
    Linux中删除特殊名称文件的多种方式
    mybatis Invalid bound statement (not found)错误解决办法
    Maven 上传文件 Error creating bean with name 'multipartResolver':
    Maven Could not open ServletContext resource [/WEB-INF/applicationContext.xml]
    JS 获取json key和value
    spring 注释
    HttpServletRequest 对文件上传的支持
  • 原文地址:https://www.cnblogs.com/lfri/p/9936998.html
Copyright © 2011-2022 走看看