首發於摸魚世界
狀壓dp,即狀態壓縮dp,它的精髓在於把dp過程中的乙個「狀態」用乙個二進位制數巧妙的表示出來。接下來就從一些入門的狀壓題目來感受一下狀壓的魅力吧~
洛谷p5911 [poi2004]prz
大致題意:
\(n\)個人過最大承載\(w\)重量的橋,每個人有重量\(w_i\)與過橋時間\(t_i\),多人一組時間取\(max\)。求最短過橋時間。
注意資料範圍:\(100≤w≤400,1≤t≤50,10≤w≤100,1≤n≤16\)
有沒有注意到\(n\)的資料範圍很耀眼。沒錯,這就是我們的突破口。
先想暴力記憶化搜尋,每個狀態記錄某些人過橋後的最小時間花費。為了記錄狀態,我們需要乙個\(bool\)陣列,甚至還可以瞎**回溯。
一通亂搞發現,盲目搜尋就是過不了。
於是來優化吧!
每個狀態我們除了用乙個陣列記錄每個人是否加入,還有什麼辦法嗎?
狀!態!壓!縮!
因為人數比較少,我們可以用乙個\(n\)位的二進位制數來記錄乙個狀態。所以這個數轉成十進位制最大只有\(2^\)。可以存在陣列的一維中。
考慮用\(dp[i]\)表示狀態為\(i\)下的最少時間花費。它可以由什麼狀態推導得到呢?
假設當前狀態是\(0111\),我們可以把它拆成\(\,\\)等另外兩個狀態。
那麼如何列舉呢?這就需要熟練掌握\(c++\)中的位運算子。很明顯,我們要先找到所有被狀態\(i\)包含的狀態\(j\),包含即\(j\)中所含的1在\(i\)中同乙個位置一定也是1,但反過來就不一定了。
比如\(1010\)可以延伸到:\(1010,1000,0010,0000\)四個狀態。
**怎麼實現呢?非常的amazing啊:for(int j=i;j>=0;j=(j-1)&i)
先給狀態\(j\)減去乙個1,再和\(i\)進行按位與,同時保證了不會漏枚和\(j\)被\(i\)包含。
那另外乙個狀態呢?進行按位異或就好了。比如上面的\(0111⊕0100=0011\),就可以把狀態\(i\)拆成\(j\)與\(i⊕j\)兩種了。
於是就有了:
\(dp[i]=min(dp[i],dp[j]+t[i⊕j])(w[i]<=w)\)
其中\(t[i]\)預處理為狀態\(i\)時的時間花費,\(w[i]\)預處理為狀態\(i\)時的重量和,初始值\(dp[i]=inf\)。
於是這道題就完成了。
單乙個一維表示狀態的題,應該還不算難
\(code:\)
#include#define inf 0x3f3f3f3f
using namespace std;
const int n=(1<<16)+1;
int dp[n],ti[n],we[n];
int t[17],c[17];
int main()
dp[i]=inf;
} dp[0]=0;
for(int i=0;i<1<=0;j=(j-1)&i)
printf("%d\n",dp[(1大致題意:
其中,\(1≤n,m≤20\)。
一道簡單的狀壓dp但是好像可能會mle。但是只是練習狀壓dp的話是一道好題。
考慮用\(dp[i][j]\)表示狀態\(i\)下,有\(j\)頭牛都選擇了合適位置的方案數。狀態儲存穀倉的選擇情況。
所以初值為\(dp[0][0]=1\)。列舉每一頭牛\(j\),在\(i\)狀態下如果他喜歡的穀倉\(v\)被占用了(\(i\&1<<(v-1)\)),那麼我們就\(dp[i][j]+=dp[i\hat{}1<<(v-1)][j-1]\),即把原有的那頭牛移開並把自己放進去,所以把原來的方案數累加上來。
最後\(ans=\sum_^ dp[i][n]\)。
\(code:\)
#includeusing namespace std;
int dp[1<<20][21];
int a[21];
int s[21][21];
int main()
dp[0][0]=1;
for(int j=1;j<=n;j++)
for(int i=1;i<1《洛谷 p1278 單詞遊戲
大致題意:
給定\(n(n≤16)\)個單詞,任意選擇其中單詞,要求為後乙個單詞的開頭必須與前乙個單詞的末尾字元相同。求最大能選擇的單詞總長度。
依然狀壓唄。
設\(dp[i][j]\)表示\(i\)狀態下以字元\(j\)結尾的最大單詞總長度。其中狀態\(i\)壓入了這\(n\)個單詞的選擇情況。
當然,首尾字元因為只有五個,我們一一對映到1-5或者0-4是沒有問題的,我為了方便,就直接把它們減去'a'作為標識就行了。記\(a[j],b[j]\)分別標識第\(j\)個單詞的首尾標識,\(l[j]\)記錄第\(j\)個單詞的長度,即對答案的貢獻。
再看轉移方程。如果當前第\(j\)個單詞沒有被選入列舉的狀態\(i\),那麼
\(dp[i+2^j][b[j]]=max\\)
其中初始值為\(dp[2^i][b[i]=l[i]\)
注意對於每乙個\(dp\)值,都去對\(ans\)取個\(max\)就好了。
\(code:\)
#includeusing namespace std;
const int n=20;
string x;
int a[n],b[n],l[n];
int f[1<<17][27];
int n;
int main()
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
for(int s=0;s<=1<<16;s++)
if(!(s&1
return 0;
}
狀壓DP入門
洛谷題號p1896 在n n的棋盤裡面放k個國王,使他們互不攻擊,共有多少種擺放方案。國王能攻擊到它上下左右,以及左上左下右上右下八個方向上附近的各乙個格仔,共8個格仔。題解 首先暴搜可肯定是超時的所以我門考慮狀壓 因為每個數都可以用二進位制表示出來 二進位制中01可以表示當前行放的棋子的位置 以及...
狀壓DP入門題
學習狀壓之前必須要熟練掌握位運算 位運算名 符號效果 and 按位與如果兩個相應的二進位制位都為1,則該位的結果值為1,否則為0 l or 按位或兩個相應的二進位制位中只要有乙個為1,該位的結果值為1 xor 按位異或 單身狗操作 若參加運算的兩個二進位制位值相同則為0,否則為1 取反 一元運算子,...
狀壓DP入門記錄
poj 3254 農場主john新買了一塊長方形的新牧場,這塊牧場被劃分成m行n列 1 m 12 1 n 12 每一格都是一塊正方形的土地。john打算在牧場上的某幾格里種上美味的草,供他的奶牛們享用。遺憾的是,有些土地相當貧瘠,不能用來種草。並且,奶牛們喜歡獨佔一塊草地的感覺,於是john不會選擇...