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;
}
 
         
         
        

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM