dp+博弈。首先我们发现,背包竟然只有21个。。。这样的话我们就可以用状态压缩表示当前状态都还剩下哪些背包了。然后对于当前状态,有两个转移方向:
1、枚举背包,如果当前背包对cooker有影响的话,我们就会得到魔法石tmp个,这样的话下一步就还是当前人拿,这样的话就是dp[sta] = max(tmp + dp[sta ^ (1 << i)])。
2、如果当前背包对cooker没有影响的话,那么如果下一个人拿到的最小的话,那么对于当前人来说就是这种情况能拿到的最大值了。于是dp[sta]=max(sum - dp[sta^(1 << i)]);(这样写关键是因为,最终得到的魔法石的总数是确定的)
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
#include<cmath>
#include<queue>
#include<map>
#define REP(i, n) for(int i=0; i<n; i++)
#define FF(i, a, b) for(int i=a; i<b; i++)
#define FD(i, a, b) for(int i=a; i>=b; i--)
#define LL long long
#define PB push_back
#define CLR(a, b) memset(a, b, sizeof(a))
using namespace std;
const int B = 23;
const int N = (1 << B);
bool vis[N];
int dp[N], b, g, s, n;
int sum[N], hav[B];
vector<int> bg[B];
int calc(int tmp)
{
int c[B], ret = 0; CLR(c, 0);
for(int i = 0 ; i < b; i ++)
{
if((1 << i) & tmp)
{
for(int j = 0; j < hav[i]; j ++)
{
c[bg[i][j]] ++;
ret += c[bg[i][j]] / s;
c[bg[i][j]] %= s;
}
}
}
return ret;
}
int ok(int tmp, int i)
{
return sum[tmp ^ (1 << i)] - sum[tmp];
}
int dfs(int sta, int sum)
{
if(vis[sta]) return dp[sta];
vis[sta] = 1;
int &ret = dp[sta];
ret = 0;
for(int i = 0; i < b; i ++)
{
if(sta & (1 << i))
{
int tmp = ok(n ^ sta, i);
if(tmp)
{
ret = max(ret, tmp + dfs(sta ^ (1 << i), sum - tmp));
}
else
{
ret = max(ret, sum - dfs(sta ^ (1 << i), sum));
}
}
}
return ret;
}
int main()
{
while(scanf("%d%d%d", &g, &b, &s), b + g + s)
{
for(int i = 0; i < b; i ++)
{
scanf("%d", &hav[i]);
bg[i].clear();
for(int j = 0; j < hav[i];j ++)
{
int tmp;
scanf("%d", &tmp);
bg[i].PB(tmp);
}
}
for(int i = 1; i < (1 << b); i ++)
{
sum[i] = calc(i);
}
n = (1 << b) - 1;CLR(vis, 0);
printf("%d\n", 2 * dfs(n, sum[n]) - sum[n]);
}
}