Traveling Salesman among Aerial Cities 旅行商(TSP)问题

题目链接:点我

问题:

给你n个点的坐标(x,y,z)。从点(a,b,c) 到另一个点 (p,q,r) 的距离是:|pa|+|qb|+max(0,rc)

问你从一个点为起点,找一条能经过其他所有点的路径,最后回到起点(除了起点可以经过两次,其他所有点只能经过一次

问你这个环的长度最小是多少

问题求解:

假设从顶点s出发,令d(i, V)表示从顶点i出发经过V(是一个点的集合)中各个顶点一次且仅一次,最后回到出发点s的最短路径长度。

我们使用dist(i,j)表示从i点到j点的距离

1、当V是空集的时候,d(i,V)就表示直接从i点回到了s点,也就是dist(i,s)

2、当V不是空集的时候,那么就是对子问题的最优求解。你必须在V这个城市集合中,尝试每一个,并求出最优解。

d(i,V)=min(d(k,V-{k})+dist(i,k))     (V-{k}表示在V集合中把k点去掉

这也就是dp方程了

 

复杂度:

2n*n2

 

代码:

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <vector>
#include <map>
#include <queue>
#include <set>
#include <ctime>
#include <cstring>
#include <cstdlib>
#include <cmath>
#define min(a,b) ((a>b)?b:a)
using namespace std;
typedef long long ll;
const int maxn = 25 + 10;
const long long ll_INF=0x3f3f3f3f3f3f3f3fLL;
const long long INF=0x3f3f3f3f;
int n,w[maxn][maxn],dp[maxn][1<<20],m;
vector<int>path;
//dp[i][j]保存顶点i到状态j最后回到起始点的最小距离
struct Point
{
    int x,y,z;
} point[maxn];
int dist(int i,int j)
{
    return abs(point[i].x-point[j].x)+abs(point[i].y-point[j].y)+max(0,point[j].z-point[i].z);
}
void TSP()
{
    //我们把起点看作0号节点
    for(int i=0; i<n; ++i)
    {
        //初始化dp数组
        dp[i][0]=w[i][0];
    }
    //按照dp方程求解
    for(int j=1; j<m; ++j) //状态
    {
        for(int i=0; i<n; ++i)
        {
            dp[i][j]=INF;
            if( ((j >> (i-1)) & 1) == 1) //这样判断而不是j>>i这样判断,是因为可以直接得到答案就在dp[n][m-1]
            {
                //要不然对于dp[n][m-1]的状态肯定每一位都是1,如果换成上面那个判断,那么这个状态的值就是
                continue;  //INF(因为continue掉了
            }
            for(int k=1; k<n; ++k)
            {
                if( ((j >> (k-1)) & 1) == 0)
                {
                    continue;
                }
                if( dp[i][j] > w[i][k] + dp[k][j^(1<<(k-1))])
                {
                    dp[i][j] = w[i][k] + dp[k][j^(1<<(k-1))];
                }
            }
        }
    }
}
//判断结点是否都以访问,不包括0号结点
bool isVisited(bool visited[])
{
    for(int i = 1 ; i<n ; i++)
    {
        if(visited[i] == false)
        {
            return false;
        }
    }
    return true;
}
//获取最优路径,保存在path中,根据动态规划公式反向找出最短路径结点
void getPath()
{
    //标记访问数组
    bool visited[n] = {false};
    //前驱节点编号
    int pioneer = 0,minn = INF, S = m - 1,temp ;
    //把起点结点编号加入容器
    path.push_back(0);

    while(!isVisited(visited))
    {
        for(int i=1; i<n; i++)
        {
            if(visited[i] == false && (S&(1<<(i-1))) != 0)
            {
                if(minn > w[i][pioneer] + dp[i][(S^(1<<(i-1)))])
                {
                    minn = w[i][pioneer] + dp[i][(S^(1<<(i-1)))] ;
                    temp = i;
                }
            }
        }
        pioneer = temp;
        path.push_back(pioneer);
        visited[pioneer] = true;
        S = S ^ (1<<(pioneer - 1));
        minn = INF;
    }
}
//输出路径
void printPath()
{
    cout<<"最小路径为:";
    vector<int>::iterator  it = path.begin();
    for(it ; it != path.end(); it++)
    {
        cout<<*it<<"--->";
    }
    //单独输出起点编号
    cout<<0;
}
int main()
{
    //printf("%d\n",(3>>(-1)));  6
    scanf("%d",&n);
    m=(1<<(n-1));
    for(int i=0; i<n; ++i)
    {
        scanf("%d%d%d",&point[i].x,&point[i].y,&point[i].z);
    }
    for(int i=0; i<n; ++i)
    {
        for(int j=0; j<n; ++j)
        {
            if(i==j) w[i][j]=0;
            else
                w[i][j]=dist(i,j);
        }
    }
    TSP();
    printf("%d\n",dp[0][m-1]);
    //下面用于输出路径
//    getPath();
//    printPath();
    return 0;
}

 

posted @ 2020-10-30 15:53  kongbursi  阅读(148)  评论(0编辑  收藏  举报