很容易想到一个很朴素的状态表示,f[i, j, k, l]表示前i个房间住了j个男的k个女的以及l对夫妇。显然TLE+MLE!
现在让我们考虑一下这道题的一些有意思的性质。
-
大部分情况下,把夫妇拆掉会比合起来住更优。考虑一个2人间,如果合起来住,占一间,拆开来和别人住(假设人够多的话),也是占一间;如果是3人间或者更多人一个房间的话,那么合起来住之能够在1间n人间里面住2个人,拆开住却能够在2个n人间里面住2n个人。不管如何,拆开住的房间利用率总是不比合起来住。
-
但是有一种特殊情况要考虑。比方说恰巧有3対夫妇,有3个2人间,如果按照上面安排的话,就住不下了。但是如果我们让一对夫妇住在一起,那么剩下的人就可以按照上面的做了。这种情况只出现在奇数对夫妇的时候。
根据上述性质,我们可以把l那一维缩减一下,变成0或者1,因为最多安排一对夫妇住在一起。转移方程:
f[i, j, k, 0] =
min { f[i-1, j, k, 0],
price[i] + min{ f[i-1, j-p, k, 0],
f[i-1, j, k-p, 0] }
}
f[i, j, k, 1] =
min { f[i-1, j, k, 1],
price[i] + min{ f[i-1, j-1, k-1, 0],
f[i-1, j-p, k, 1],
f[i-1, j, k-p, 1] }
}
时间复杂度O(room
malefemale
2),勉强不会超时。但是MLE!解决办法可以:滚动数组,或者跟01背包一样,倒着做,直接覆盖,空间复杂度降到O(malefemale)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
| #include <cstdio>
#include <cstring>
#include <algorithm>
int TestData, male, female, room, couple, cap[501], price[501], f[501][501][2];
void solve()
{
scanf("%d%d%d%d", &male, &female, &room, &couple);
for (int i = 1; i <= room; ++i)
scanf("%d%d", cap+i, price+i);
for (int j = 0; j <= male; ++j)
memset(f[j], 63, sizeof(f[j]));
f[0][0][0] = 0;
for (int i = 1; i <= room; ++i)
for (int j = male; j >= 0; --j)
for (int k = female; k >= 0; --k)
{
int min0 = 0x7FFFFFFF, min1 = 0x7FFFFFFF;
for (int p = 0; p <= cap[i]; ++p)
{
#define UP(x, y) if (y < x) x = y;
if (j-p >= 0)
{
UP(min0, f[j-p][k][0]);
UP(min1, f[j-p][k][1]);
}
if (k-p >= 0)
{
UP(min0, f[j][k-p][0]);
UP(min1, f[j][k-p][1]);
}
}
if (j-1 >= 0 && k-1 >= 0 && cap[i] >= 2) UP(min1, f[j-1][k-1][0]);
UP(f[j][k][1], price[i]+min1);
UP(f[j][k][0], price[i]+min0);
#undef UP
}
int ans = f[male][female][0];
if (couple) ans = std::min(ans, f[male][female][1]);
if (ans >= 0x3F3F3F3F)
printf("Impossiblen");
else
printf("%dn", ans);
}
int main()
{
for (scanf("%d", &TestData); TestData--; ) solve();
}
|