Headmaster‘s Headache

该博客介绍了如何解决算法竞赛中的一道问题,即如何以最小成本聘请教师使得每个科目都有人教授。博主分享了一种利用动态规划的方法,通过维护两个集合来分别表示至少有一人和至少有两人教授的科目,并给出状态转移方程。代码中展示了如何实现这一策略,包括使用记忆化搜索优化复杂度。最后,博主提供了完整的C++代码示例来求解此问题。
摘要由CSDN通过智能技术生成

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值