P9339 [JOISC 2023] 曲奇 题解
Description
莉婕喜欢做饼干。她制作了 \(N\) 种饼干。第 \(i\) 种饼干有 \(A_i\) 个。为了出售她制作的饼干,她将它们装入盒子中。但是,应该满足以下条件。
-
对于每个盒子,其中的饼干种类应不同。
-
对于每个盒子,其中的饼干数量应等于以下 \(M\) 个数字之一:\(B_1,B_2,⋯ ,B_M\)。
编写一个程序,给出莉婕制作的饼干信息和将饼干装箱的条件,确定是否可能将所有饼干包装到盒子中。此外,如果可以将所有饼干包装在盒子中,则您的程序应输出最少的盒子数量。
\(1\leq M\leq N\leq 15000,\sum A_i\leq 15000\)。
Solution
假设有 \(k\) 个盒子,\(C_i\) 表示第 \(i\) 个盒子的大小,考虑怎么判定一组 \(C_i\) 合法。
首先 \(\sum C_i=\sum A_i\)。
然后把 \(C_i\) 从大到小排序,那么前 \(j\) 个盒子需要放 \(\sum_{i=1}^{j}{C_i}\) 个饼干,而每种饼干在每个盒子最多放一个,所以需要满足 \(\sum_{i=1}^{j}{C_i}\leq\sum_{i=1}^{n}{\min\{A_i,j\}}\)。
这个东西是必要条件,下面用 Hall 定理证明这个也是充分的。
首先建出二分图,左部点是盒子,右部点是饼干,\(S\) 向第 \(i\) 个盒子连流量 \(C_i\) 的边,第 \(i\) 种饼干向汇点连流量 \(A_i\) 的边,第 \(i\) 个盒子向第 \(j\) 种饼干连流量为 \(1\) 的边。
根据 Hall 定理,对于任意盒子的子集 \(S\),需要满足 \(\sum_{i\in S}{C_i}\leq\sum_{j=1}^{n}{\min\{A_j,|S|\}}\),最劣情况一定是从大到小排序后的一段后缀,即上面的结论。
然后就可以 dp 了。
设 \(f_{i,j,k}\) 表示已经考虑了前 \(i\) 大的盒子,选了 \(j\) 个,总和为 \(k\) 是否可行。
用 bitset 转移是 \(O\left(\frac{S^3}{w}\right)\) 的,但是由于 \(j\leq \frac{S}{B_i}\),所以总状态是 \(S\log S\) 级别,复杂度优化为 \(O\left(\frac{S^2\log S}{w}\right)\)。
时间复杂度:\(O\left(\frac{S^2\log S}{w}\right)\)。
Code
#include <bits/stdc++.h>
// #define int int64_t
const int kMaxN = 1.5e4 + 5;
int n, m, mm, s;
int a[kMaxN], b[kMaxN], c[kMaxN], lim[kMaxN], coef[kMaxN];
std::vector<std::bitset<kMaxN>> f[kMaxN];
std::bitset<kMaxN> g[kMaxN];
void getcc(int i, int j, int k) {
// std::cerr << i << ' ' << j << ' ' << k << '\n';
if (i == m + 1) return assert(!j && !k);
while (1) {
if (j < f[i + 1].size() && f[i + 1][j][k]) return getcc(i + 1, j, k);
c[++mm] = b[i], --j, k -= b[i];
}
}
void dickdreamer() {
std::cin >> n;
for (int i = 1; i <= n; ++i) {
std::cin >> a[i];
++coef[1], --coef[a[i] + 1], lim[a[i] + 1] += a[i];
s += a[i];
}
std::cin >> m;
for (int i = 1; i <= m; ++i) std::cin >> b[i];
for (int i = 1; i <= s; ++i) {
lim[i] += lim[i - 1], coef[i] += coef[i - 1];
}
for (int i = 0; i <= s; ++i) {
lim[i] += i * coef[i];
g[i].reset(), g[i].flip();
g[i] ^= (g[i] << (lim[i] + 1));
// std::cerr << lim[i] << " \n"[i == s];
}
f[m + 1].resize(1), f[m + 1][0].reset(), f[m + 1][0][0] = 1;
for (int i = m; i; --i) {
f[i].resize(s / b[i] + 1);
for (int j = 0; j < f[i].size(); ++j) f[i][j].reset();
for (int j = 0; j < f[i + 1].size(); ++j) f[i][j] = f[i + 1][j];
for (int j = 0; j < f[i].size(); ++j) {
f[i][j] &= g[j];
if (j + 1 < f[i].size()) f[i][j + 1] |= (f[i][j] << b[i]);
}
// std::cerr << "!!! " << b[i] << ' ' << f[i].size() << '\n';
}
int cnt = -1;
for (int i = 0; i < f[1].size(); ++i) {
if (f[1][i][s]) {
cnt = i; break;
}
}
if (!~cnt) return void(std::cout << "-1\n");
getcc(1, cnt, s);
std::cout << cnt << '\n';
std::priority_queue<std::pair<int, int>> q;
for (int i = 1; i <= n; ++i) q.emplace(a[i], i);
for (int i = 1; i <= cnt; ++i) {
std::vector<int> vec;
for (int j = 1; j <= c[i]; ++j) {
vec.emplace_back(q.top().second);
q.pop();
}
std::cout << c[i] << ' ';
for (auto x : vec) {
std::cout << x << ' ';
--a[x];
assert(a[x] >= 0);
// q.emplace(a[x], x);
if (a[x]) q.emplace(a[x], x);
}
std::cout << '\n';
}
}
int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

浙公网安备 33010602011771号