【UVA10187】Headmaster's Headache(校长的烦恼)

题面

某个学校要师资力量不够,要招收新的老师,第一行给出s、m、n,现在在任的老师有m个,然后给出m行表示每个老师的信息,分别是该老师的工资,以及可教授的课程(个数不一定),然后在n行表示可招收的老师信息,同样是工资和课程,s表示该学校开售的课程,问,最少花多少钱可以使得该学校开设的s个课程每个课程有两个老师任教。
m<=20,n<=100,s<=8

分析

这题好经典,紫书上的,记忆化搜索+状压dp,值得一学。
我看到的比较喜欢的一种解法是用集合的运算。其实是三个集合,无人教集合s0,一人教集合s1,两人教集合s2.最后要从s0满,s1、s2空算出s0、s1空,s2满的答案。
用dp[i][s0][s1][s2]表示计算到第i个人为止,三个集合状态为s0,s1,s2时的最小花费。然而知道了s1,s2可以计算出s0,所以为节省空间优化为dp[i][s1][s2]
每次转移需要更新这三个集合,比如四门课,现在s0是1001,s1是0100,s2是0010,如果下一个老师可以教第2、4门课,即0101
把新老师加入集合后,s0-->1000 s1-->0001 s2-->0110
怎么得到的呢?用 m0表示他能教而且当前没有人教的课 ,m1表示他能教且当前只有一个人教的课 。
则 m0=0101&1001=0001 m1=0100&0101=0100,而可以用m0和m1来更新s0,s1,s2.
s0=s0^m0=1000 没有人教是1,有人教是0,而0001中1是可以教,0是不能教的,刚好相反,原来无人教的1遇到有人教的1会变成0,表示有人教。
s1=(s1^m1)|m0=0001 s1第一部分与s0的更新同理,但是或了s0,表示从s0那儿升级上来有一人教的
s2=s2|m1=0110  因为s2就只能从m1那儿升级上来了。

分析

#include<bits/stdc++.h>
using namespace std;
#define N 200
#define INF 1234567890
int s,n,m,x,mx;
int w[N],can[N],dp[N][1<<8][1<<8];

inline int dfs(int i,int s0,int s1,int s2)
{
    if(i==n+m+1) return s2==mx?0:INF;
    int &ans=dp[i][s1][s2];
    if(ans>=0)return ans;
    ans=INF;
    if(i>n)ans=dfs(i+1,s0,s1,s2);
    int m0=s0&can[i],m1=s1&can[i];
    s0^=m0,s1=(s1^m1)|m0,s2|=m1;
    ans=min(ans,w[i]+dfs(i+1,s0,s1,s2));
    return ans;
}

inline void init()
{
    mx=(1<<s)-1;
    memset(dp,-1,sizeof(dp));
    memset(can,0,sizeof(can));
}

int main()
{
    while(scanf("%d%d%d",&s,&n,&m)&&s)
    {
        init();
        for(int i=1;i<=n+m;i++)
        {
            scanf("%d",&w[i]);
            while(1)
            {
                scanf("%d",&x);
                can[i]=can[i]|(1<<x-1);
                char c=getchar();
                if(c=='
')break;
            }
        }
        int ans=dfs(0,mx,0,0);
        printf("%d
",ans);
    }
}
“Make my parents proud,and impress the girl I like.”
原文地址:https://www.cnblogs.com/NSD-email0820/p/9790348.html