才学了状压dp就赶紧来做一道,结果这道题卡了我两天才过...
关键是这道题的状态转移方程,我看了紫书上的大概思路才写出来。
用s1,s2表示集合,分别为一个老师上的课程,两个老师上的课程。
那么d[s1][s2]=min(d[s1^a][s2^b]+请这个老师用的钱)其中a,b分别是这个老师如果请了,那么会出现的一个老师上的课和两个老师上的课。
刚刚写的时候只用了i,s1,s2三个参数,发现不方便表示a,b所以加了一个s0,表示没有选的课程,即一个老师也没有的课程。
代码和注释如下:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<climits>
#define up(i,a,b) for(int i=a;i<b;i++)
#define dw(i,a,b) for(int i=a;i>b;i--)
#define upd(i,a,b) for(int i=a;i<=b;i++)
#define dwd(i,a,b) for(int i=a;i>=b;i--)
#define local
typedef long long ll;
const double esp = 1e-6;
const double pi = acos(-1.0);
const long long INF = 0x3f3f3f3f;
using namespace std;
int m, s, n;
int d[1000][(1<<8)+1][(1<<8)+1];
int st[100000];
int my[100000];
int dp(int i, int s0, int s1, int s2)
{
if (i == m + n)//这个函数代表要用的钱,当第i个老师请了的话
{
if (s2 == (1 << s) - 1)//如果s2已经满了那么就不要钱了不用请老师
return 0;
else return INT_MAX;
}
int &ans = d[i][s1][s2];
if (ans >= 0) return ans;
ans = INT_MAX;
if (i >= m) ans = dp(i + 1, s0, s1, s2);//如果不请第i个老师的话
int m0 = st[i]&s0;//第i个老师特有的
int m1 = st[i] & s1;//与只上一节课重复的,那么这节课变成了两个老师上课了
s0 ^= m0;
s1 = (s1^m1) | m0;//记得s1要去掉已经变成了两节课的课程
s2 |= m1;
ans = min(ans, dp(i + 1, s0, s1, s2)+my[i]);//如果请了,判断是否是最小的
//cout << ans << endl;
return ans;//返回结果
}
int main()
{
#ifdef local
freopen("D:\\data.in.txt", "r", stdin);
freopen("D:\\data.out", "w", stdout);
#endif
while (cin >> s >> m >> n && s&&m&&n)
{
memset(d, -1, sizeof(d));
memset(my, 0, sizeof(my));
memset(st, 0, sizeof(st));
up(i, 0, m+n)//要注意一下这道题的输入
{
cin >> my[i];
char ch;
while ((ch = getchar()) != '\n')
{
if (isdigit(ch))
{
st[i] |= 1 << ((ch - '0')-1);
// cout << st[i] << endl;
}
}
}
cout << dp(0, (1<<s)-1, 0, 0)<<endl;
}
return 0;
}