状态压缩是指使用计算机二进制位来存储状态,一般用法是将二进制串当做一个集合,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;
}
}