1. 程式人生 > 實用技巧 >最短Hamilton路徑

最短Hamilton路徑

題目

題目

思路

前排提示:這裡的下標全部從\(1\)開始

思路怎麼可能時二進位制DP呢?這才第一章啊。

應該吧

然後我用了BFS暴力搜尋。

首先,我們發現題目是一個無向完全圖,雖然我做完還是不知道a[x,y]+a[y,z]>=a[x,z]有什麼用其實這也不重要,我用BFS的管我屁事。好像DP的也無關緊要吧

都是從\(0\)開始那我們就不管了,因為要求每個點都走一遍,所以我們肯定要用某種方式把我們走過的點表示出來,沒錯,就是二進位制,而這裡\(n<=20\),所以二進位制第\(i\)位就代表了這個點有沒有走過,同時也有一個很重要的資訊還要記錄就是我們當前走到了哪個點,不難想出,利用這兩個資訊就足以用這個路徑進行轉移了,什麼,你說有很多條路徑滿足這個資訊?這就是壓縮資訊的精髓了,把一些類似的路徑取一個最小值進行轉移,同時不影響答案(插頭DP其實也是類似的思路)。

仔細想想你會發現,滿足同兩個資訊的一些路徑在轉移上都是一樣的,且最後儘可能是走過的邊權和最小的路徑可以轉移成功,所以只要用邊權和最小的路徑代表這些路徑就行了。

然後對於\(f[i][j]\)\(i\)記錄路徑,\(j\)表示目前到了哪個點)的轉移就是:
\(f[i+(1<<(k-1))][k]=f[i][j]+a[j][k]\)

然後就可以愉快的BFS啦。

但是BFS比較容易BFS,如果想要降空間的話還是用DP吧,但是時間複雜度是一樣的,只不過DP利用二進位制的一個性質:走過\(k\)個點的路徑轉移完後,\(k+1\)個點的路徑的\(f\)值就全部出來了,所以只要把含有\(k\)

\(1\)的全部\(f\)轉移完之後去轉移\(k+1\)\(1\)就行了,當然,DP也可以找這條路徑是由哪些路徑轉移來的進行DP,時間複雜度也是一樣的(如\(f[3][2]\)會主動去找\(f[1][1]\)來更新自己),只不過一個是更新別人,一個是找別人更新自己罷了(還有一些更神奇的轉移順序我就不說了)。

程式碼

#include<cstdio>
#include<cstring> 
#define  N  22
#define  M  1100000
using  namespace  std;
int  n,a[N][N],f[M][N]/*M表示走過的裝填,N表示目前所在的位置*/;
struct  qmq
{
	int  x,y;/*y表示狀態,x表示位置*/
}list[16000000];int  head,tail;
inline  int  mymin(int  x,int  y){return  x<y?x:y;}
int  main()
{
	scanf("%d",&n);
	int  limit=(1<<n)-1;
	for(int  i=1;i<=limit;i++)
	{
		for(int  j=1;j<=n;j++)f[i][j]=999999999;
	}
	for(int  i=1;i<=n;i++)
	{
		for(int  j=1;j<=n;j++)scanf("%d",&a[i][j]);
	}
	list[1].x=1;list[1].y=1;f[1][1]=0;
	head=1;tail=1;
	while(head<=tail)
	{
		qmq  x=list[head++];
		for(int  i=1;i<=n;i++)
		{
			if((x.y&(1<<(i-1)))==0)//沒走過
			{
				qmq  now;now.y=x.y^(1<<(i-1));now.x=i;
				if(f[now.y][now.x]==999999999)list[++tail]=now;//沒有走過就加入佇列
				f[now.y][now.x]=mymin(f[now.y][now.x],f[x.y][x.x]+a[x.x][i]);
			}
		}
	}
	int  ans=f[limit][n];
	printf("%d\n",ans);
	return  0;
}