校长的烦恼 Headmaster's Headache

在这里插入图片描述
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 0M+N1,即 0 ∼ M − 1 0\sim M-1 0M1为在职教师, M ∼ M + N − 1 M\sim M+N-1 MM+N1为应聘教师。
初始化
d p = − 1 dp=-1 dp=1表示未计算过
转移方程

  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]idp[i+1][s1][s2]+teachers[i].Costi)
    其中, s 1 ′ , s 2 ′ s_1',s_2' s1,s2为聘用此人后的新 s 1 , s 2 s_1,s_2 s1,s2
  2. 枚举在职教师时,只能聘用
    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+N1],此时为考虑所有教师后,因此若此时 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+N1到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;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值