题目链接:
[UVA 10817]Headmaster's Headache[状压DP]
题意分析:
校长需要s门课,每门至少有两名老师来教,所以他现在想要招老师啦。当然,校长手头本来就有m个老师,每个老师都教着一个或多个课程,这些老师是不能解雇的,必须用。然后现在又n个老师来应聘,每个都有价格和他们能教的课程,校长希望花最少的钱达到他的目标,问:最少多少钱呢?
解题思路:
嘛!s <= 8,赤裸裸的暗示(?)。我们可以设置两个集合,s1对应还需要一个老师教的科目,s2对应已经足够老师教的科目,状态设为dp[i][s1][s2]:代表到第i个老师为止,校长最少需要花多少钱达到目标。当i < m 时,我们必须选择,状态只能更新为添加老师,当i >= m时, 就可以更新为选或不选老师两种状态。
个人感受:
第一次写这道题,不会,然后补题,看别人代码。昨天又一次碰到,除了知道是状压外,依稀只记得集合存科目这种事。今天重新做一遍,看了看状态,自己写了转移,感触颇多啊。之前看刘汝佳代码,他设置了三个集合,整个运算符用得极其魔幻(?),当时理解了半天。这次自己写的转移,只用到了其中的两个集合,发现第一个完全可以省略嘛XD,然后两行就完成了转移XD。重做出奇迹啊。。。。
具体代码如下:
#include<iostream>
#include<cstring>
#include<string>
#include<sstream>
using namespace std;
const int INF = 0x7f7f7f7f, MAXN = 131;
int s, m, n, teach[MAXN], cost[MAXN], dp[MAXN][1 << 8][1 << 8];
int DP(int i, int s1, int s2)
{
if (i == m + n) return s2 == (1<<s) - 1 ? 0 : INF; //每个科目都至少两个老师了,那么就不需要再花钱了
int &ret = dp[i][s1][s2];
if (ret >= 0) return ret;
ret = INF;
if (i >= m) ret = DP(i + 1, s1, s2); //不选
s2 |= (s1 & teach[i]); //老师能教,并且差一个老师,那么一并运算,剩下的就是满足条件的科目
s1 |= teach[i]; //或上去,没人教的科目肯定变成差一个人教
ret = min(ret, cost[i] + DP(i + 1, s1, s2)); //选
return ret;
}
int main()
{
while (cin >> s >> m >> n && s)
{
cin.get();
string ss;
int x;
for (int i = 0; i < m + n; ++i)
{
getline(cin, ss);
stringstream sss(ss);
sss >> cost[i];
teach[i] = 0;
while(sss >> x)
{
teach[i] |= 1 << (x - 1);
}
}
memset(dp, -1, sizeof dp);
//for (int i = 0; i < m + n; ++i) cout << cost[i] << ':' << teach[i] << endl;
cout << DP(0, 0, 0) << '\n';
}
return 0;
}