一道狀壓板子題題解By 雲歲月書 2020/7/16

原題:

機器人剛剛探查歸來,探險隊員們忽然發現本身的腳下出現了一朵朵白雲,把他們託向了空中。一陣飄飄然的感受事後,隊員們發現本身被傳送到了一座空中花園。 「遠道而來的客人,咱們是守護Nescafe之塔的精靈。若是大家想拜訪護法和聖主的話,就要由咱們引路。所以,大家是否是該給咱們一點禮物呢?」 隊員們往遠處一看,發現花園中有N個木箱,M條柔軟的羊毛小路,每條小路的兩個端點都是木箱,而且經過它須要ti的時間。隊員們須要往每一個木箱中放一份禮物,不過因爲精靈們不想讓花園被過多地踩踏,所以運送禮物的任務最多隻能由兩位探險隊員完成。 兩位探險隊員同時從1號木箱出發,能夠在任意木箱處結束運送。運送完畢時,每隻木箱應該被兩位隊員中至少一人訪問過。運送任務所用的時間是兩人中較慢的那我的結束運送任務的時間。請問完成運送禮物的時間最快是多少呢?

題目大意:

給你一個有邊權的無向圖,設其的點集 \(U\),求從點 \(1\) 出發的兩條路徑 \(d_1,d_2\),使得 \({d_1}\cup{d_2} = U\)

數據範圍:

$ 1 \le N \le 18,1 \le wi\le 1000$

思考過程:

看題目猜想是圖論題,再看數據範圍,確定是狀態壓縮 \(DP\)

而後咱們開始設計狀態。

咱們先考慮一下在 \(DP\) 的過程當中有哪些東西須要記錄。

首先,必定須要傳遞的是當前過程當中遍歷了多少點。c++

其次,咱們還須要傳遞當前節點處已用了多少時間。數組

最後,還有咱們走到當前節點遍歷了哪些節點。網絡

難點:是兩我的在走。

這讓我想到了洛谷的 P1006 ,爲了解決它咱們把狀態設計成了四維(固然也能夠三維,甚至二維。甚至能夠用網絡流.。),兩我的的位置都準確記錄了。spa

可是仔細考慮一下,不難發現上述設計的狀態很難在規定的空間(\(256 MB\))內完成 \(DP\) 數組的定義,或是乾脆設計不出來,因此咱們只能另闢蹊徑,從另外一個角度來設計狀態。設計

回到咱們的題目大意:code

求從點 \(1\) 出發的兩條路徑 \(d_1,d_2\),使得 \({d_1}\cup{d_2} = U\)get

就能夠發現其是兩我的什麼是無所謂的,咱們主要求的是兩條路徑。string

因而求一下全部路徑,而後選兩條 \(s_1,s_2\),使得 \({s_1}\cup{s_2} = U\)it

求路徑,且並不限制對於點的重複遍歷,所以咱們對原來的圖是什麼樣的並不在乎,咱們能夠先作一次 \(Floyd\) 的求出全源最短路來求路徑。io

先估一下時間複雜度是 \(\Theta({n^2}{2^n}+{n^3})\) ,空間複雜度 \(O({n2^n}+{n^2})\) ,能夠過。

狀態轉移方程的設計。

\[\Large\sum\limits_{i=1}^{2^n}{\sum\limits_{j=1}^{n}{\sum\limits_{k=1}^{n}{dp{_{{i|2^{k-1}},k}} = Min(dp{_{{i|2^{k-1}},k}},dp_{i,j}+G_{j,k})}}} \]

\(G_{j,k}\) 表示 \(Floyd\) 數組。

\(dp_{i,j}\)\(i\) 爲狀態壓縮的狀態,若 \(i\) 的二進制表示下第 \(k\)\(1\) ,則代表節點 \(k\) 已遍歷。

\(dp_{i,j}\) 表示狀態 \(i\) 狀況下在 \(j\) 點。

這個方程別看嚇人,其實並不難,他本質就是由基礎狀態向其餘狀態擴散的過程。

值得注意的是,咱們所獲得的路徑並非最短路徑,因此要單開一個數組(\(Path\))記錄路徑。

設初始狀態 \(dp_{1,1}\)\(0\) ,其他狀態爲 \(0\)

for(int i = 1 ; i < TOT; ++i)
		for(int j = 1 ; j <= n ; ++j)
			if(i & (1 << (j-1)))//能夠從i點轉移。 
			{
				for(int k = 1; k <= n ; ++k)
					//if(G[j][k] <= 0x3f3f3f3f && !(i&(1<<k-1)))
						dp[i|(1<<(k-1))][k] = Min(dp[i|(1<<(k-1))][k],dp[i][j] + G[j][k]);
				Path[i] = Min(Path[i],dp[i][j]);	
			}

如何求的最短路,其實也不難,咱們能夠經過相似 \(Floyd\) 的方法求出真正的最短路。

for(reg int k = 1; k < TOT; ++k)
	for(reg int i = 1; i <= n ; ++i)
		Path[k] = Min(Path[k],Path[k|(1<<(i-1))]);

最後,再統計一下答案。

for(reg int i = 1; i < TOT; ++i)
		ans = Min(ans,Max(Path[i],Path[(1<<n)-1-i]));

完整代碼

# include <cstdio>
# include <cstring>
# define N 18
# define reg register
# define INF 0x3f3f3f3f

inline int Read()
{
	int x = 0;char ch = getchar();
	
	while(ch < '0' || ch > '9') ch = getchar();
	
	while(ch >= '0' && ch <= '9'){x = x*10 + (ch^48);ch = getchar();}
	
	return x;
}

const int M = (1 << N);

inline int ABS(const int A){return A < 0 ? -A : A;}
inline int Min(const int a,const int b){return a < b ? a : b;}
inline int Max(const int a,const int b){return a > b ? a : b;}

int n,m,G[N + 42][N + 42],dp[M + 42][20],Path[M + 42],ans = 0x3f3f3f3f,TOT;

int main()
{
	n = Read();m = Read();
	
	memset(dp,0x3f,sizeof(dp));
	memset(G,0x3f,sizeof(G));
	memset(Path,0x3f,sizeof(Path));
	
	dp[1][1] = 0;//初始化。 
	
	TOT = 1<<n;
	
	for(reg int i = 1,x,y; i <= m ; ++i)
	{
		scanf("%d%d",&x,&y);
		
		G[x][y] = G[y][x] = Read();
	}

	for(reg int k = 1; k <= n ; ++k)
		for(reg int x = 1; x <= n ; ++x)
			for(reg int y = 1; y <= n ; ++y)
				G[x][y] = Min(G[x][y],G[x][k] + G[k][y]);
	
	for(int i = 1 ; i < TOT; ++i)
		for(int j = 1 ; j <= n ; ++j)
			if(i & (1 << (j-1)))//能夠從i點轉移。 
			{
				for(int k = 1; k <= n ; ++k)
					//if(G[j][k] <= 0x3f3f3f3f && !(i&(1<<k-1)))
						dp[i|(1<<(k-1))][k] = Min(dp[i|(1<<(k-1))][k],dp[i][j] + G[j][k]);
				Path[i] = Min(Path[i],dp[i][j]);	
			}
			
	for(reg int k = 1; k < TOT; ++k)
		for(reg int i = 1; i <= n ; ++i)
			Path[k] = Min(Path[k],Path[k|(1<<(i-1))]);
	
	for(reg int i = 1; i < TOT; ++i)
		ans = Min(ans,Max(Path[i],Path[(1<<n)-1-i]));
	
	printf("%d",ans);
	
	return 0;
}

總結一下:

其實這道題除了路徑哪裏的轉換之外,其他的就是一道標準狀壓模板(~~可是我死在了路徑上~~)。
相關文章
相關標籤/搜索