10817 - Headmaster's Headache(校长的烦恼)

经典的状态压缩DP 。

根据DP的阶段定义,我们需要枚举每一个教师进行递推,但是由于每个教师可以教授的课程是复杂多样的,所以使得状态变得难以转移 。那么要怎么样表示状态呢? 显然增加一两个维度是无法胜任的,所以我们可以用二进制枚举子集的方法,用一个整数通过位运算充当一个集合 。 C++提供的位运算符极像对集合的操作,我们恰好可以利用这一点 。

用d[i][s1][s2]表示考虑了前i个人时的最小花费 。 其中s1表示恰有一个人教的科目集合,s2表示至少有两个人教的科目集合 。因为由这两个量就可以推出一个人也没有教的科目集合,所以用这两个量已经可以表示所有状态了。  然后就是各种精彩的位运算操作 ,代码中最难的部分大概就是这两行位运算 :

int m0 = st[i] & s0, m1 = st[i] & s1; 
s0 ^= m0; s1 = (s1 ^ m1) | m0; s2 |= m1;
既然要选择当前这名教师,那么他所教的课程就要使得集合改变 。  m0就是指他能教而且当前没有人教的课 ,m1指他能教且当前只有一个人教的课 。 ^运算符使得相同的为0,不同的为1 ,所以使得那些他能教的课在集合s0中变成了0 ,也就是说这些课升级为恰有一人教的课。  那么显然还要更新s1,(s1 ^ m1)和前面说的作用一样,使得这些课升级为至少有两个人教的,但是别忘了还有一些课升级为恰有一人教的,所以用|(或)操作,将这些课加进s1 中,  s2当然也要加进s1中升级的元素 。

位运算不但节省空间,而且节省时间, 所以多花一些时间练习用位运算写代码还是很值得的 。

#include<bits/stdc++.h>
using namespace std;
const int INF = 100000000;
int s,n,m,a,c[125],st[125],d[125][1<<8][1<<8];
int dp(int i,int s0,int s1,int s2) {
    if(i == m+n) return s2 == (1<<s) - 1 ? 0 : INF;//如果仍未达到要求,无解
    int& ans = d[i][s1][s2];
    if(ans >= 0) return ans;
    ans = INF;
    if(i >= m) ans = dp(i+1,s0,s1,s2);//不选该教师
    int m0 = st[i] & s0, m1 = st[i] & s1; //对集合的位运算操作,极为经典 。要熟练掌握
    s0 ^= m0; s1 = (s1 ^ m1) | m0; s2 |= m1;
    ans = min(ans,c[i] + dp(i+1,s0,s1,s2));//选
    return ans;
}
int main() {
    while(~scanf("%d%d%d",&s,&m,&n)&&s) {
        memset(d,-1,sizeof(d));
        for(int i=0;i<m+n;i++) {
            scanf("%d",&c[i]);
            st[i] = 0;
            while(true) {
                scanf("%d",&a);
                a--; st[i] = st[i] | (1 << a);//将该元素加入集合中
                char c = getchar();
                if(c == 10) break;//换行的ascii码
            }
        }
        int ans = dp(0,(1<<s)-1,0,0) ;
        printf("%d\n",ans);
    }
    return 0;
}




你可能感兴趣的:(ACM,uva)