题意分析:
校长需要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; }