首先,我們先來認識一下什么叫做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這個點的狀態,那么就很好求解了對吧
題目大意:多組數據,給定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; }
