Headmaster’s Headache
传送门
参考:算法竞赛入门经典(第二版)P286.
本题做法很多,但是我是不会的,一种相对容易实现的做法是:用两个集合s1恰好表示有一个人教的科目的集合,s2表示至少有两个人教的科目集合,而d(i,s1,s2)表示己经考虑了前i个人的最少消费。注意,所有人一起从0开始编号,则编号0~m-1是在职教师,m到n+m-1是应聘者,状态转移方程d(i.s1.s2)=min{d(i+1,s1’,s2’)+c[i],d(i+1,s1,s2)},其中第一项表示聘用,第二项表示不聘用。当i>=m时,状态方程才出现第二项。这里s1’s2’分别表示招聘第i个人后s1和s2的新值,下面代码中st[i]代表第i个人能交科目的集合,(注意输入中科目从1开始编号,而代码的其他部分从科目0开始编号,因此输入时要转换一下),下面的代码用到了一个技巧:记忆搜索中有一个参数s0,表示没有任何人能教的科目集合。这个参数并不需要记忆(因为有了s1,s2就能算出s0),仅是为了编程的方便,最终结果是dp(0,(1<<s)-1,0,0),因为初始时所有科目都没有人教
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <vector>
#include <cstdlib>
#include <queue>
using namespace std;
#define ll long long
const int N=(1<<8)+8;
const int inf=5e7;
const int mod=1e8;
int s,m,n;//m个教师,n个求职者,s个课程
int d[125][N][N],st[125],c[125],num;
char p;
const int dir[8][2]= {{-1,0},{1,0},{0,-1},{0,1},{-1,-1},{-1,1},{1,-1},{1,1}};
//把所有人一起从0编号,0-m-1是在职教师,m~n+m-1是应聘者
int dp(int nn,int s0,int s1,int s2)
{
if(nn==m+n+1)
return s2==(1<<s)-1?0:inf;//如果s2恰好等于全部课程的集合时,已经满足题意,不需要花钱
int &ans=d[nn][s1][s2];//引用,代替
if(ans>=0)
return ans;
ans=inf;
if(nn>m)
ans=dp(nn+1,s0,s1,s2);//不选第i个应聘者,由于选i应聘者会导致s0,s1,s2改变,因此先初始化成不选
int m0=st[nn]&s0;//只有第i个应聘者会教的课程;
int m1=st[nn]&s1;//第i个应聘者也会教的课程
s0=s0^m0;//在s0集合中去除所有i应聘者会教的课程,就是m0
s1=(s1^m1)|m0;//m1代表的所有课程变为了至少两个人会教,从s1中出去,同时加上m0
s2=s2|m1;//将m1添加到s2
ans=min(ans,c[nn]+dp(nn+1,s0,s1,s2));//选第i个应聘者,取较小者。
return ans;
}
int main()
{
while(cin>>s>>m>>n)
{
if(!s) return 0;
num=m+n;
for(int i=1; i<=num; i++)
for(int j=0; j<N; j++)
for(int k=0; k<N; k++)
d[i][j][k]=-1;
for(int i=1;i<=num;i++)
{
int ans=0;
scanf("%d",&c[i]);
p=getchar();
while(p!='\n')
{
if(p!=' ')
ans+=(1<<(p-49));
p=getchar();
}
st[i]=ans;
}
cout<<dp(1 , (1<<s) - 1, 0 , 0)<<endl;
}
return 0;
}