狀態壓縮是指使用計算機二進制位來存儲狀態,一般用法是將二進制串當做一個集合,bit位代表集合中的元素,bit位取值表示元素是否在集合中。n位二進制串可以描述2^n種集合(狀態),因此對於n的取值是相當嚴格的。狀態壓縮DP中需要使用各種位運算來描述狀態轉換。所以需要對位運算的使用有一定的瞭解,下面是一些巧妙的運用。
- 把一個數字x最靠右的第一個1去掉 : x = x & (x-1)
- 判斷數字x中是否存在相鄰的1 : x & (x >> 1)
需要注意,位運算的優先級通常比較低,如果不熟悉這些優先級,建議多使用括號避免出錯。
UVa10817-Headmaster's Headache是一道典型的狀態壓縮DP。
題意:
某校有某個教師和n個求職者,需講授s個課程(1 ≤ s ≤ 8, 1 ≤ m ≤ 20, 1 ≤ n ≤ 100)。已知每人工資c(10000 ≤ c ≤ 50000)和能教的課程集合,要求支付最少的工資使得每門課至少有兩個教師能教。在職教師不能辭退。
分析:
本題使用狀態壓縮顯然應該講課程作爲要壓縮的狀態,考慮每門課程至少有兩個教師教。狀態定義如下:
- 16位長二進制串代表狀態,bit位代表課程,其中低八位代表有一位老師教該課程,高八位代表有兩個老師教該課程。
- dp[n][s]代表考慮前n個老師的情況下,維護一個狀態最少需要多少開銷。
- 每個老師維護一個狀態,代表其能教導的課程。
此時狀態轉移方程如下:
int changeState(int from, int by)
{
int first = (~from) & by; // 第一個上這門課的老師
int second = from & by; // 第二個上這門課的老師
int to = (from | first | (second << 8)); // 增加這個老師後的狀態
return to;
}
代碼:
// 狀態壓縮DP
#include <iostream>
#include <sstream>
#include <cstdio>
#include <bitset>
#include <string>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAX_S = 8;
const int MAX_N = 100;
const int MAX_STATE = (1 << 16) - 1;
const int INF_ = 0x3f3f3f3f;
int begin_state; // 初始狀態
int begin_cost; // 初始開銷
int states[MAX_N+1]; // 應聘者狀態
int costs[MAX_N+1]; // 應聘者工資
int dp[MAX_N+1][MAX_STATE+1]; // 考慮前i個應聘者時,維護每個狀態所需最小開銷
int changeState(int from, int by)
{
int first = (~from) & by; // 第一個上這門課的老師
int second = from & by; // 第二個上這門課的老師
int to = (from | first | (second << 8)); // 增加這個老師後的狀態
return to;
}
void input(int s, int m, int n)
{
begin_state = ((1 << 16) - 1) - ((1 << (s+8))-1) + ((1 << 8) - 1) - ((1 << s) - 1);
begin_cost = 0;
for (int i = 0; i < m; i++)
{
string str;
int cost, lesson;
cin >> cost;
begin_cost += cost;
getchar();
getline(cin, str);
stringstream ss(str);
while (ss >> lesson)
{
lesson--;
begin_state = changeState(begin_state, (1 << lesson));
}
}
for (int i = 1; i <= n; i++)
{
string str;
int state = 0, lesson;
cin >> costs[i];
getchar();
getline(cin, str);
stringstream ss(str);
while (ss >> lesson)
{
lesson--;
state = changeState(state, (1 << lesson));
}
states[i] = state;
}
}
int solve(int s, int m, int n)
{
memset(dp, INF_, sizeof(dp));
dp[0][begin_state] = begin_cost;
for (int i = 1; i <= n; i++)
{
for (int j = 0; j <= MAX_STATE; j++)
{
int to_state = changeState(j, states[i]);
dp[i][j] = min(dp[i][j], dp[i-1][j]); // 不增加這個老師
dp[i][to_state] = min(dp[i][to_state], dp[i-1][j]+costs[i]); // 增加這個老師
}
}
return dp[n][MAX_STATE];
}
int main()
{
int s, m, n;
while (true)
{
cin >> s >> m >> n;
if (s == 0) break;
input(s, m, n);
cout << solve(s, m, n) << endl;
}
}