校长的烦恼 Headmaster's Headache
UVA10817
定义:$s_0$为没人教的科目的集合,$s_1$为恰好有一人教的科目的集合,$s_2$为至少有两人教的科目的集合。则定义$dp[i][s_1][s_2]$为考虑前i名教师时科目情况为相应科目情况为$s_1,s_2$时的最小花费。$AllSet$为全集。这里的集合采用二进制表示法。$i$的编号从$0\sim M+N-1$,即$0\sim M-1$为在职教师,$M\sim M+N-1$为应聘教师。
初始化:
其中,$s_1’,s_2’$为聘用此人后的新$s_1,s_2$
- 枚举在职教师时,只能聘用
边界条件:
当$i==M+N$时,即$dp[M+N]$要赋值给$dp[M+N-1]$,此时为考虑所有教师后,因此若此时$s_2==AllSet$,则说明此时所有课都由至少两名老师,无需再额外聘用老师。否则返回$inf$,以便做$min$运算。注意:实现中由于递归(先进后出)的性质,实际上枚举顺序是从$M+N-1$到0。
AC代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
using namespace std;
int S, M, N;
constexpr static int inf = 0x3f3f3f3f;
int dp[130][1 << 8][1 << 8];
int AllSet;
struct Employee {
int Cost, CourseSet;
}teachers[130];
bool Input() {
scanf("%d", &S);
if (!S) {
return false;
}
scanf("%d%d", &M, &N);
AllSet = (1 << S) - 1;
for (int i = 0; i < M + N; ++i) {
scanf("%d", &teachers[i].Cost);
teachers[i].CourseSet = 0;
while (getchar() != '\n') {
int Course;
scanf("%d", &Course);
teachers[i].CourseSet |= (1 << Course - 1);
}
}
return true;
}
int DP(int i, int s0, int s1, int s2) {
if (i == M + N) {
return s2 == AllSet ? 0 : inf;
}
int& ans = dp[i][s1][s2];
//ans!=-1,即此时改dp元素已经被枚举到
if (~ans) {
return ans;
}
ans = inf;
//如果枚举的是应聘者,要考虑不聘用他的情况
if (i >= M) {
ans = DP(i + 1, s0, s1, s2);
}
int
//m0=没有老师教的科目集合与当前老师教的科目集合的交集
&&m0 = teachers[i].CourseSet & s0,
//m1=只有一个老师教的科目的集合与当前老师教科目集合的并集
&&m1 = teachers[i].CourseSet & s1;
//相当于s0-m0,即s0应当去掉当前老师能教的科目
s0 ^= m0;
//s1应去掉s1中与当前老师教的科目重合的科目(此时多了一个老师教,这些科目移到s2),并且加上原来没有老师教的但当前老师能教的科目
s1 = (s1 ^ m1) | m0;
//s2加上原来就有一个老师教现在当前老师又能教的科目
s2 |= m1;
ans = min(ans, teachers[i].Cost + DP(i + 1, s0, s1, s2));
return ans;
}
int main() {
while (Input()) {
memset(dp, -1, sizeof(dp));
printf("%d\n", DP(0, AllSet, 0, 0));
}
return 0;
}