- 题意:校长的烦恼 Headmaster’s Headache
- s很小,不难想到用状态压缩处理。但是我对状态压缩的应用还是固守在了处理棋盘上,一开始想的是把每门学科用两位二进数表示,但是没法做状态转移。
- 正解:用两个二进数表示当前状态,第一个数s1表示恰有一个人教的学科集合(某一位为0表示不在集合中,某一位为1表示在集合中),另一个数表示至少有两个人教的学科的集合,d(i,s1,s2)表示已经考虑了前i-1个人时(前i-1人聘用情况确定即s1s2确定,后面人的聘用情况任意)此状态下的最小花费,答案为d(1,0,0),为方便计算同时维护当前的未教课程集合s0。
- 代码(紫书上的记忆化搜索写法特别好)
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=(1<<8)+8,inf=5e7 ;
int s,m,n,d[125][maxn][maxn],st[125],sub,c[125],num;
char cc;
int dp(int nw,int s0,int s1,int s2)
{
if(nw==n+m+1) return s2==(1<<s)-1?0:inf;
int& ans = d[nw][s1][s2];
if(ans>=0) return ans;
ans = nw>m?dp(nw+1,s0,s1,s2):inf;
int m0 = s0&st[nw],m1 = s1&st[nw];
s0^=m0;s1=(s1^m1)|m0;s2|=m1;
ans=min(ans,c[nw]+dp(nw+1,s0,s1,s2));
return ans;
}
void intt()
{
for(int i=1;i<=num;i++)
for(int j=0;j<maxn;j++)
for(int k=0;k<maxn;k++)
d[i][j][k]=-1;
}
int main()
{
while(cin>>s>>m>>n)
{
if(!s) return 0;
num=n+m;
intt();
for(int i=1;i<=num;i++)
{
int ans=0;
scanf("%d",&c[i]);
cc=getchar();
while(cc!='\n')
{
if(cc!=' ')
ans+=(1<<((cc-48)-1));
cc=getchar();
}
st[i]=ans;
}
cout<<dp(1,(1<<s)-1,0,0)<<endl;
}
}