UVA10817
定义:
s
0
s_0
s0为没人教的科目的集合,
s
1
s_1
s1为恰好有一人教的科目的集合,
s
2
s_2
s2为至少有两人教的科目的集合。则定义
d
p
[
i
]
[
s
1
]
[
s
2
]
dp[i][s_1][s_2]
dp[i][s1][s2]为考虑前i名教师时科目情况为相应科目情况为
s
1
,
s
2
s_1,s_2
s1,s2时的最小花费。
A
l
l
S
e
t
AllSet
AllSet为全集。这里的集合采用二进制表示法。
i
i
i的编号从
0
∼
M
+
N
−
1
0\sim M+N-1
0∼M+N−1,即
0
∼
M
−
1
0\sim M-1
0∼M−1为在职教师,
M
∼
M
+
N
−
1
M\sim M+N-1
M∼M+N−1为应聘教师。
初始化:
d
p
=
−
1
dp=-1
dp=−1表示未计算过
转移方程:
- 枚举应聘者时
d p [ i ] [ s 1 ] [ s 2 ] = m i n ( d p [ i + 1 ] [ s 1 ] [ s 2 ] 不 聘 用 第 i 名 教 师 , d p [ i + 1 ] [ s 1 ′ ] [ s 2 ′ ] + t e a c h e r s [ i ] . C o s t 聘 用 第 i 名 教 师 ) dp[i][s_1][s_2]=min(\\ dp[i+1][s_1][s_2]不聘用第i名教师,\\ dp[i+1][s_1'][s_2']+teachers[i].Cost聘用第i名教师\\) dp[i][s1][s2]=min(dp[i+1][s1][s2]不聘用第i名教师,dp[i+1][s1′][s2′]+teachers[i].Cost聘用第i名教师)
其中, s 1 ′ , s 2 ′ s_1',s_2' s1′,s2′为聘用此人后的新 s 1 , s 2 s_1,s_2 s1,s2 - 枚举在职教师时,只能聘用
d p [ i ] [ s 1 ] [ s 2 ] = d p [ i + 1 ] [ s 1 ′ ] [ s 2 ′ ] + t e a c h e r s [ i ] . C o s t dp[i][s_1][s_2]=dp[i+1][s_1'][s_2']+teachers[i].Cost dp[i][s1][s2]=dp[i+1][s1′][s2′]+teachers[i].Cost
边界条件:
当
i
=
=
M
+
N
i==M+N
i==M+N时,即
d
p
[
M
+
N
]
dp[M+N]
dp[M+N]要赋值给
d
p
[
M
+
N
−
1
]
dp[M+N-1]
dp[M+N−1],此时为考虑所有教师后,因此若此时
s
2
=
=
A
l
l
S
e
t
s_2==AllSet
s2==AllSet,则说明此时所有课都由至少两名老师,无需再额外聘用老师。否则返回
i
n
f
inf
inf,以便做
m
i
n
min
min运算。注意:实现中由于递归(先进后出)的性质,实际上枚举顺序是从
M
+
N
−
1
M+N-1
M+N−1到0。
AC代码:
#include<iostream>
#include<string>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cmath>
#include<map>
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;
}