描述
题解
状压 DP 。
设 dp[i][j][k][s] 表示为前 i 本书拿走 j 本剩下的书状态为 k 最后一本书的高度是 s 的最少类数。这里的状态指的是剩下的书所有的高度,而不是剩下的书的序列状态,为什么要这样设置呢?因为我们设置的抽出的书并没有直接考虑如何插入,而是放到最后统一插入,如果抽出的书中有高度 x ,而剩下的书也有高度 x 自然是不需要产生更多类的,否则就需要产生新的类。所以最后遍历一遍 dp[cur] ,判断不同状态 k 所对应的被抽书状态与 k 的差集,这个差集就是需要产生的新的类。
神乎其技的状压 DP ,注意边界问题,注意滚动数组。
代码
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
const int MAXB = 8;
const int MAXN = 110;
const int MAXM = 1 << MAXB;
const int LIMIT = 25;
const int INF = 0x3f3f3f3f;
int n, m, ans;
int book[MAXN];
int cnt[MAXM];
int state;
int dp[2][MAXN][MAXM][MAXB + 1];
void init()
{
for (int i = 0; i < MAXM; i++)
{
for (int j = 0; j < MAXB; j++)
{
if (i & (1 << j))
{
cnt[i]++;
}
}
}
}
int main()
{
init();
int ce = 0;
while (~scanf("%d%d", &n, &m) && n + m)
{
int mx = state = 0;
for (int i = 1; i <= n; i++)
{
scanf("%d", book + i);
book[i] -= LIMIT;
if (book[i] > mx)
{
mx = book[i];
}
state |= (1 << book[i]);
}
++mx;
int tot = (1 << mx);
memset(dp[1], 0x3f, sizeof(dp[1]));
dp[1][0][1 << book[1]][book[1]] = 1;
dp[1][1][0][mx] = 0;
for (int i = 2; i <= n; i++)
{
int cur = i & 1;
int pre = 1 - cur;
memset(dp[cur], 0x3f, sizeof(dp[cur]));
for (int j = 0; j <= m && j < i; j++)
{
for (int k = 0; k < tot; k++)
{
for (int s = 0; s <= mx; s++)
{
if (dp[pre][j][k][s] == INF)
{
continue;
}
int tmp = k | (1 << book[i]);
if (j < m)
{
dp[cur][j + 1][k][s] = min(dp[cur][j + 1][k][s], dp[pre][j][k][s]); // 取最后一本
}
if (s == book[i])
{
dp[cur][j][k][s] = min(dp[cur][j][k][s], dp[pre][j][k][s]);
}
else
{
dp[cur][j][tmp][book[i]] = min(dp[cur][j][tmp][book[i]],
dp[pre][j][k][s] + 1);
}
}
}
}
}
int cur = n & 1;
int ans = n, st;
for (int j = 0; j <= m; j++)
{
for (int k = 0; k < tot; k++)
{
for (int s = 0; s < mx; s++)
{
if (dp[cur][j][k][s] != INF)
{
st = state ^ k; // 抽走的就是额外的类
ans = min(ans, cnt[st] + dp[cur][j][k][s]);
}
}
}
}
printf("Case %d: %d\n\n", ++ce, ans);
}
return 0;
}