题目
思路
依然是集合DP的基本思路,此处的集合是课程的集合。
用s0表示没人教的课程集合,s1表示恰好有一个人教的课程集合,s2表示有至少两个人教的课程集合。由于s0可以由s1和s2算出来,所以不用记录在状态里。(虽然状态很复杂,但能简单一些是一些)
所以用d(i,s1,s2)表示前i个人,在s1,s2情况下的最小花费。此处应该注意的是,子集二进制表示法要求元素由0开始,题目给的是1,转化一下即可。
1.状态定义:d(i,s1,s2),在s1,s2情况下已经考虑了前i个人的最小花费。
2.初状态:
3.答案:d(0,0,0)。
(添加了s0的dp函数中,答案的参数为dp(0,(1 << s)-1,0,0))
4.状态转移方程:
(第一项表示选这个人,第二项表示不选)
5.复杂度分析: (大概我瞎猜的)
关于输入,本题的输入是行无限的:
2 2 2
10000 1
20000 2
30000 1 2
40000 1 2
0 0 0
之前一直不会这种输入的解决办法,本题从LRJ那学到了:
int x;
string line;
while(getline(cin, line)) {
stringstream ss(line);
ss >> s >> m >> n;
if(s == 0) break;
for(int i = 0; i < m+n; i++) {
getline(cin, line);
stringstream ss(line);
ss >> c[i];
st[i] = 0;
while(ss >> x) st[i] |= (1 << (x-1));
}
关于本题中复杂的二进制子集操作,应该掌握:
int m0 = st[i] & s0, m1 = st[i] & s1;
// m0,教师能教的但还没人教的课程。m1,教师能教的但有一个人教了的课程。
s0 ^= m0; s1 = (s1 ^ m1) | m0; s2 |= m1;
ans = min(ans, pay[i] + dp(i + 1, s0, s1, s2));
给人感觉子集擦做里,^就是-,|就是+,&就是找交集。
代码
(UVA崩了,所以本代码并没有拿去评测)
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <sstream>
#define _for(i,a,b) for(int i = (a); i<(b); i++)
#define _rep(i,a,b) for(int i = (a); i<=(b); i++)
using namespace std;
const int INF = 1000000;
const int maxn = 28 + 2;
const int maxs = 8+1;
int s, m, n, d[maxn][1<<maxs][1<<maxs], pay[maxn], st[1<<maxs];
// st[i]表示,序号为i的教师能教授的课程集合,此处直接用了集合。
int dp(int i, int s0, int s1, int s2) {
if (i == m + n)
return s2 == (1 << s) - 1 ? 0 : INF;
int &ans = d[i][s1][s2];
if (ans != -1) return ans;
ans = INF;
if (i >= m) ans = dp(i + 1, s0, s1, s2); // 不选
int m0 = st[i] & s0, m1 = st[i] & s1; // m0,教师能教的但还没人教的课程。m1,教师能教的但有一个人教了的课程。
s0 ^= m0; s1 = (s1 ^ m1) | m0; s2 |= m1;
ans = min(ans, pay[i] + dp(i + 1, s0, s1, s2)); // 选
return ans;
}
int main() {
while (scanf("%d%d%d", &s, &m, &n) == 3 && s) {
string line;
getline(cin, line);
_for(i, 0, m+n) {
getline(cin, line);
stringstream ss(line);
ss >> pay[i];
st[i] = 0;
int x;
while (ss >> x) st[i] |= (1 << (x - 1));
// 为了表示方便,子集表示时往往从0开头。
// 此处的|=为添加元素或子集
}
memset(d, -1, sizeof(d));
printf("%d\n", dp(0, (1 << s) - 1, 0, 0));
}
return 0;
}