CF1519F Chests and Keys
Part1:转化
考虑什么情况下能保证 Alice 获胜。
记 Alice 给箱子 \(i\) 配的锁的集合为 \(N(i)\),则 Bob 需要满足:
\[\forall s\subseteq{1,2,\dots,n},(\sum_{i\in s} a_i)\leq(\sum_{j\in(\cup_{i\in s}N(i))} b_j)
\]
说人话,就是对于任意箱子的子集 \(s\),Bob 的收益小于买来所需钥匙的花费。
容易发现这像是一个点带权的 \(\text{Hall}\) 定理的形式。考虑使用其逆定理重新刻画问题与条件。
- 将所有箱子 \(i\) 所代表的点拆成 \(a_i\) 个颜色为 \(i\) 的点;
同理,将锁 \(j\) 所代表的点拆成 \(b_j\) 个颜色为 \(j\) 的点。 - 假若颜色为 \(i\) 与颜色为 \(j\) 的点之间存在至少一条边,那么 Alice 会花费 \(c_{i,j}\) 的代价。
- 答案就是最小权完备匹配。
这个转化的充分性显然。
必要性可以考虑反证。我们刻画什么条件下原题意的条件满足,现在不满足。
唯一的可能就是存在颜色 \(i\),我们只选了其拆出的 \(a_i\) 个点中的部分。这里可以考虑,每个拆出的同颜色点,本质是相同的,所以这并不会影响正确性。具体的可以用归纳法进一步详细证明。
Part2:设计具体做法
现在来考虑这个新问题怎么做。记 \(f_{i,s}\) 表示,考虑了前 \(i\) 种颜色的点,每个锁拆成的点中还没被匹配的个数(压缩成 \(m\) 位 \(5\) 进制数 \(s\)),最小总花费是多少。
- 状态数总量为 \(n\times 5^m\)
转移就枚举当前 \(a_i\) 个点和右边每种颜色点匹配个数。有效转移不多,可以利用 \(\text{DFS}\) 搜出。
最终答案就是:
\[\min_{s\subseteq {1,2,\dots,n}} f_{n,s}
\]
Part3:代码
点击查看代码
#include <bits/stdc++.h>
#define FL(i, a, b) for (int i = (a); i <= (b); ++i)
#define FR(i, a, b) for (int i = (a); i >= (b); --i)
using namespace std;
typedef vector<int> vi;
constexpr int N = 8, V = 5, M = 2e4;
constexpr int INF = 0x3f3f3f3f;
int n, m, a[N], b[N], pwV[N], c[N][N];
int f[N][M];
int GetState(vi v) {
int s = 0;
FL(i, 0, m - 1) {
s += v[i] * pwV[i];
}
return s;
}
vi GetArray(int s) {
vi v(m, 0);
FL(i, 0, m - 1) {
v[i] = s % V;
s /= V;
}
return v;
}
void Input() {
scanf("%d %d", &n, &m);
FL(i, 1, n) {
scanf("%d", &a[i]);
}
FL(i, 1, m) {
scanf("%d", &b[i]);
}
FL(i, 1, n) {
FL(j, 1, m) {
scanf("%d", &c[i][j]);
}
}
}
vi v;
void Trans(int i, int s, int j, int r, int w) {
if (j == m) {
if (!r) {
f[i][s] = min(f[i][s], w);
}
return;
}
FL(t, 0, min(v[j], r)) {
v[j] -= t;
Trans(i, s - t * pwV[j], j + 1, r - t, w + (t? c[i][j + 1] : 0));
v[j] += t;
}
}
void DP() {
pwV[0] = 1;
FL(i, 1, m) {
pwV[i] = pwV[i - 1] * V;
}
memset(f, 0x3f, sizeof(f));
v = vi(b + 1, b + m + 1);
f[0][GetState(v)] = 0;
FL(i, 1, n) {
FL(s, 0, pwV[m] - 1) {
if (f[i - 1][s] >= INF) {
continue;
}
v = GetArray(s);
Trans(i, s, 0, a[i], f[i - 1][s]);
}
}
}
void CalcAns() {
int ans = INF;
FL(s, 0, pwV[m] - 1) {
ans = min(ans, f[n][s]);
}
printf("%d\n", ans >= INF? -1 : ans);
}
int main() {
Input();
DP();
CalcAns();
return 0;
}

浙公网安备 33010602011771号