TSP问题之状压dp法

首先,我们先来认识一下什么叫做TSP问题

旅行商问题,即TSP问题(Traveling Salesman Problem)又译为旅行推销员问题、货郎担问题,是数学领域中著名问题之一。假设有一个旅行商人要拜访n个城市,他必须选择所要走的路径,路径的限制是每个城市只能拜访一次,而且最后要回到原来出发的城市。路径的选择目标是要求得的路径路程为所有路径之中的最小值。假设这个n很小,我们就可以使用状态压缩的方法求解,在一般的TSP问题中的用状压求解的题目,我们可以定义一个dp数组,dp[i][v],其中v表示一个集合,dp[i][v]表示到i这个点经过v中所有点的最小路径.

假设我们从s出发,最后再回到s

1.那么最开始,只有dp[s][{s}]=0,其余均等于inf

2.其他情况下,dp[i][state]=min(dp[i][state],dp[j][state']+c[j][i])

3.最后我们的结果,ans=min(ans,dp[i][state]+c[i][s]),因为我们要求的是一个环的最短路,所以还要加上回来的距离

那么还有一个问题,我们要如何存下这个集合,当然是用状态压缩的方法,s|1<<(k),表示由原来的状态s转移到加上k这个点的状态,那么就很好求解了对吧

POJ3311

题目大意:多组数据,给定n,一个起点0,以及这n+1个点之间的距离,求从起点出发经过每个点一次,再回到起点的最短距离.注意到n<=10,我们可以使用状压dp来做

思路:首先先预处理出这n+1个点之间的最短距离,因为n很小,我们可以使用floyed来处理.然后就是套我上面的说的三种情况,具体可以代码中的注解

 

#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#define in(i) (i=read())
using namespace std;
const int inf=0x3f3f3f;
int read()
{
    int ans=0,f=1;
    char i=getchar();
    while(i<'0'||i>'9'){if(i=='-') f=-1; i=getchar();}
    while(i>='0'&&i<='9') {ans=(ans<<1)+(ans<<3)+i-'0';i=getchar();}
    return ans*f;
}
int n;
int dp[13][1<<13];
int mp[13][13];
void floyed()
{
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                mp[i][j]=min(mp[i][j],mp[i][k]+mp[k][j]);
    return;
}
int main()
{
    while(1) {
        int ans=inf; in(n);
        if(!n) break;
        n++;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                in(mp[i][j]);//输入每两个点之间的距离
        floyed();//求出n+1个点两两之间的最短距离
        memset(dp,inf,sizeof(dp));
        dp[1][1]=0;//默认以1为起点,集合内最开始状态为1<<(1-1)=1,所以dp[1][1]=0
        for(int i=1;i<(1<<n);i++)//枚举状态
            for(int j=1;j<=n;j++)//枚举每个点
                if((i&(1<<(j-1)))!=0)//判断这个是否在集合中
                    for(int k=1;k<=n;k++)//如果不在就以它为中转点转移
                        if(!(i&(1<<(k-1))))
                            dp[k][i|(1<<(k-1))]=min(dp[k][i|(1<<(k-1))],dp[j][i]+mp[j][k]);//状态转移方程
        for(int i=2;i<=n;i++)
            ans=min(ans,dp[i][(1<<n)-1]+mp[i][1]);//还要回来才是一个环,因此还要加上到起点的距离
        cout<<ans<<endl;
    }
}

 

 

 

上述代码在洛谷应该是会T一个点的,因为重复使用位运算速度是会变慢的,所以我们可以提前处理出每个点左移多少位之后的数组,以及使用系统自带函数min也是很慢的\

#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#define MIN(a,b) (a)<(b)?(a):(b)
#define in(i) (i=read())
using namespace std;
const int inf=0x3f3f3f;
int read()
{
    int ans=0,f=1;
    char i=getchar();
    while(i<'0'||i>'9') {if(i=='-') f=-1; i=getchar();}
    while(i>='0'&&i<='9') { ans=(ans<<1)+(ans<<3)+i-'0';i=getchar();}
    return ans*f;
}
int dp[1<<20][21],mp[21][21],st[21];
int n;
int main()
{
    in(n);
    int ans=inf;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            in(mp[i][j]);
    memset(dp,inf,sizeof(dp));
    dp[1][1]=0; st[0]=1;
    for(int i=1;i<=20;i++) st[i]=st[i-1]<<1;//预处理
    for(int i=1;i<st[n];i++)
        for(int j=1;j<=n;j++)
            if( dp[i][j]!=dp[0][0] && i&st[j-1])
                for(int k=1;k<=n;k++)
                    if(!(i&st[k-1]))
                        dp[i|st[k-1]][k]=MIN(dp[i|st[k-1]][k],dp[i][j]+mp[j][k]);
    for(int i=2;i<=n;i++)
        ans=MIN(ans,dp[st[n]-1][i]+mp[i][1]);
    printf("%d\n",ans);
    return 0;
}

 

posted @ 2018-03-17 14:32  real_l  阅读(3268)  评论(2编辑  收藏  举报