题面
某个学校要师资力量不够,要招收新的老师,第一行给出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); } }