從旅行商問題談狀態壓縮DP

2022-05-07 21:33:12 字數 3111 閱讀 6812

乙個商品推銷員要去若干個城市推銷商品,該推銷員從乙個城市出發,需要經過所有城市後,回到出發地。應如何選擇行進路線,以使總的行程最短?請輸出最短行程。節點個數\(n\)滿足\(2 \leq n \leq 20\),路的長度小於\(1000\)。

例如這個旅行商問題,可能的路線一共有\((n-1)!\)種,如果純暴力試遍每一種方案,那必然是會超過限定時間的。那麼我們就需要一種優化方案了,它就是:狀態壓縮動態規劃。

我們知道,動態規劃是根據之前已經計算好的狀態推出後面一種狀態,這道題亦是如此。也許有人會說,跑一遍最短路不就好了嗎?但是最短路不保證走遍每乙個點,而即使我們加上了點的計數,那也不能保證從\(a\)上不存在乙個環(往回走的環),其節點個數恰好彌補了那些沒掃過的節點。而且計數的時候還沒到終點的點該如何更新呢?說不定一不小心就更沒了,而且如果把每種情況都記錄下來,是不亞於列舉的。但是仍有一些弱資料可以被水過去(noip),而且這種求最小值的狀態壓縮題甚至可以被一些優秀的隨機化演算法(模擬退火)等水過去。不過這些演算法最怕的就是求所有狀態的題目了,而狀態壓縮dp就做到了求出所有狀態這一點。

其中\(f[s][v]\)表示的是從\(v\)出發訪問所有不屬於集合\(s\)的頂點,最終回到\(0\)的路徑的權重總和的最小值。而集合\(v\)表示為所有城市的集合,不難理解從\(0\)到\(0\)就是路程為\(0\)對吧,並且再將其它所有情況均賦值為無窮大(當然不可能是無窮大啊,找乙個達不到的值即可),以防計算不存在的情況。其他的話,遞推式已經表達得很明白了,大家也可以舉幾個例子幫助自己理解(注意,裡面有些狀態是對答案沒用的,大家可以找一找,後面我會提到)。

注意這裡出現了集合表示法,但是似乎不好怎麼表示這個集合,難道用\(set\)表示嗎?那就慢了!別忘了二進位制這個好東西。

注意到我們的\(int\)可以表示到\(2^-1\),然而這裡的\(n\)才這麼大一點,那麼我們就可以在二進位制位中逐位儲存某個點「存在」和「不存在」的情況。再者,這只需涉及到二進位制的位運算,所以整體來說還是很快的。

那麼我們現在來為一些稍缺知識的同學補充一些二進位制表達集合的方法吧!

二進位制表達想必學過一些二進位制的人都知道吧,我們需要做的僅僅是摒棄它的十進位制的模樣(別影響了自己的理性思維)。假設現在有乙個集合\(s=\emptyset\),表示為二進位制就是\(00000000\)(假裝只有\(8\)位),然後我們往裡面加入\(0\),那麼在二進位制位的第\(0\)位就要改為\(1\),也就是\(s=s\cup \ =s\vee 2^0=s|1<<0\)。同理,如果往裡面加入\(6\),就是\(s|=1<<6\)(此處計算機表示法均用c++表達)。

如果是判斷\(u\)是否存在於集合\(s\)中的話,那麼只需要判斷\(s\vee 2^u\)(即c++中的\(s\and (1<< u)\))是否為真就可以了。因為後者僅第\(u\)位是\(1\),其它部分都是\(0\),而與的性質是二者都真時才為真,故達到了判斷的目的。

於是我們就不難寫出程式了。這裡以洛谷1171為例。

注意,這道題十分卡常!仍然需要優化!但請一步一步看下來,不要急於求成!

下面這段**僅能得到\(80\)分,但是開\(o2\)可以險過

#include #include #define inf 20000

using namespace std;

int f[(1<<20)][20], d[20][20];

int main()

][u]+d(v,u)|u\notin s\}

\]注意到\(min\)中的\(f[s\cup \][u]\)保證了\(\)必然在集合\(s\cup \\)裡面,有人可能會說這是廢話,但是這說明了在前面的狀態中,\(v\)必然在集合\(s\)裡面啊!因此對於\(v\)不在集合\(s\)中的情況就不必考慮了。但是,注意當\(s\)為空(程式中變為\(0\))的時候它就不會繼續走答案了,也就是說我們要特殊處理一下這種情況。

於是我們得到了\(90\)分**。

#include #define inf 20000

int f[(1<<20)][20], d[20][20];

inline int cmin(int a, int b)

int main()

][u]+d(v,u)|u\notin s, v\in s\}\]

最開始\(s\)二進位制位(位數小於\(n\))全部是\(1\)的時候,僅\(0\)號點為\(0\),其它都是\(inf\)。而有些\(inf\)是可以推到底層的!那什麼樣的情況滿足其\(f\)恒為\(inf\)呢?首先注意到\(f[v][x],x\neq 0\)的情況恒為\(inf\),那這個狀態又會推到**呢?根據遞推式:

\[f[s][v]=min\][u]+d(v,u)|u\notin s, v\in s\}

\]我們有:

\[f[s][v]=min\ ,u\notin s, v\in s\}

\]容易得到\(f[s][v]\)必然為\(inf\)(因為\(u\)只有一種可能)。

同理,對於遞推式:

\[f[s][v]=min\][u]+d(v,u)|\ \in s ,u\notin s, v\in s\}

\]\(f[s][v]\)必然由乙個比當前集合\(s\)(包含元素\(0\))多乙個元素的集合\(s'\)得來,而\(s'\)又以類似方式得來,最終它們共同的**均為:

\[f[v][u]+d(v,u)|u\neq \ ,u\notin s, v\in s

\]故對於任何滿足\(\ \in s\)的\(f[s][v]\),它們的值恒為\(inf\)。用二進位制表示法來說,只要\(s\and 1\)為真,那麼就無需考慮

那麼程式就可以「進化」了,從而拿到\(100\)分!

#include #define inf 20000

int f[(1<<20)][20], d[20][20];

inline int cmin(int a, int b)

int main()

int main()

別看了,這裡沒有缺陷,交上去照樣ac。

狀態壓縮DP 旅行商問題

題目描述 給定乙個n個頂點組成的帶權有向圖的距離矩陣d i j inf表示沒有邊 要求從頂點0出發,經過每個頂點恰好一次後再回到頂點0.問所經過的邊的總權重的最小值是多少?限制條件 題目解析 這個問題就是著名的旅行商問題 tsp 所有可能的路線有 n 1 種。這是乙個非常大的值,即使題中n已經很小了...

旅行商問題 狀態壓縮

二進位制的很多應用離不開集合這個概念,我們都知道在計算機當中,所有資料都是以二進位制的形式儲存的。一般乙個int整形是4個位元組,也就是32位bit,我們通過這32位bit上0和1的組合可以表示多大21億個不同的數。如果我們把這32位bit看成是乙個集合,那麼每乙個數都應該對應集合的一種狀態,並且每...

POJ 3311 旅行商問題 狀態壓縮 dp

最近做 dp 做的起勁啊。旅行商問題的狀態壓縮 dp i j 表示 遍歷 i 中所有城市一遍 並且現在在 j 的最短距離是多少。第乙個點可以遍歷兩次所以先不設定為1,並且這個狀態是合法狀態,這個點曾經是最困擾我的。include include include include include inc...