[luogu p2160] [SHOI2007]书柜的尺寸
P2160 [SHOI2007]书柜的尺寸 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
把书按高度从大到小排序,依次考虑放置,每一层第一个被放置的书的高度就是这一层的高度。
设 \(f(i, j, k, l)\) 表示考虑了前 \(i\) 本书,第一层宽度为 \(j\),第二层宽度为 \(k\),第三层宽度为 \(l\) 时,最小的整个书架的高度(紫题应该就在于这个状态比较难想)。
为什么会这么想,首先考虑 \(t_i\) 和 \(h_i\) 范围很小,可以猜测是 dp 的某一维,而且发现如果设成讨论每个书被放在哪里,状态的量级会高达 \(3^{70}\),这是不合适的,而这 \(3^{70}\) 个状态中其实有大量状态的第一层,第二层,第三层宽度分别完全一致(因为三层所有宽度的情况最多只有 \(2100^3\) 种,远小于 \(3^{70}\)),这种情况下转移也是一致的,可以一起处理,那就直接按照三层的宽度情况分类即可(这是本题的思维难点)。
其次,如果考虑前 \(i\) 本书,第一层高度为 \(j\),第二层高度为 \(k\),第三层高度为 \(l\) 的最小化整个书架的宽度,会发现很难转移,因为根本没有利用好开头所说的那个结论。
想到更换转移,于是有了上面的状态设计,发现这样就容易转移了。
刷表转移,对于 \(p = f(i, j, k, l)\),我们考虑对第一层进行放书,第二第三层完全同理。
下一个状态是 \(f(i + 1, j + t_i, k, l)\)。如果 \(j = 0\),把它和 \(p + h_i\) 取一个最小值,代表放了第一层放下了第一本书,我们统计上第一层的高度;否则如果 \(j \ne 0\),代表第一层已经放过一本书了,直接把它和 \(p\) 取一个最小值——显然第一层的高度已经统计在里面了。
发现时空复杂度仍然危险,考虑优化。
其实很容易发现 \(i, j, k\) 确定了 \(l\) 也就确定了,具体来说前 \(i\) 本书都放在书架中,第三层的宽度就是前 \(i\) 本书的总宽度减去第一层的宽度 \(j\) 减去第二层的宽度 \(k\)。因此第四维可以删掉。
然后这个题第一维还需要一个滚动。
统计答案就是直接大力统计最后状态。
时间复杂度 \(\mathcal{O}(n^3t^2)\),\(t\) 表示 \(t_i\) 的数量级。
这个题是 SHOI 2007,可以看到这题当时时限开了 5s,但是现在 1s 就可过,时代的进步~
/*
* @Author: crab-in-the-northeast
* @Date: 2022-09-23 21:02:08
* @Last Modified by: crab-in-the-northeast
* @Last Modified time: 2022-09-23 22:35:56
*/
#include <bits/stdc++.h>
inline int read() {
int x = 0;
bool flag = true;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
flag = false;
ch = getchar();
}
while (isdigit(ch)) {
x = (x << 1) + (x << 3) + ch - '0';
ch = getchar();
}
if(flag)
return x;
return ~(x - 1);
}
const int maxn = 75;
const int maxm = maxn * 35;
int f[2][maxm][maxm];
struct node {
int h, t;
}a[maxn];
inline bool getmin(int &a, int b) {
return b < a ? a = b, true : false;
}
inline bool getmin(long long &a, long long b) {
return b < a ? a = b, true : false;
}
int main() {
int n = read();
for (int i = 1; i <= n; ++i) {
int h = read(), t = read();
a[i] = (node){h, t};
}
std :: sort(a + 1, a + 1 + n, [](node b, node c) {
return b.h > c.h;
});
std :: memset(f[0], 0x3f, sizeof(f[0]));
f[0][0][0] = 0;
int x = 0;
for (int u = 1; u <= n; x += a[u].t, ++u) {
int i = u & 1, t = a[u].t, h = a[u].h;
std :: memset(f[i], 0x3f, sizeof(f[i]));
for (int j = 0; j <= x; ++j) {
for (int k = 0; k <= x - j; ++k) {
int p = f[i ^ 1][j][k];
getmin(f[i][j + t][k], p + (j ? 0 : h));
getmin(f[i][j][k + t], p + (k ? 0 : h));
int l = x - j - k;
getmin(f[i][j][k], p + (l ? 0 : h));
}
}
}
long long ans = LONG_LONG_MAX;
for (int i = 1; i < x; ++i)
for (int j = 1; j < x - i; ++j)
getmin(ans, 1LL * std :: max({i, j, x - i - j}) * f[n & 1][i][j]);
printf("%lld\n", ans);
return 0;
}