CF1519F Chests and Keys
这是一个初学 hall 定理的萌新写的题解,然而我一开始并没有用 hall 定理做这道题。
首先这道题显然是 hall 定理的形式,但是我注意到了所有值都很小,只有关于答案的数很大,自然想到搜索。
考虑直接爆搜,重要的是先判无解,然后考虑爆搜每条边是否要连,那么我们将边权从小到大排序搜索,然后在修改时,我们只需最多 \(2^5\) 次枚举,为了优化常数我们可以在这里状压并进行一些预处理,具体可以看代码。
然后如果当前要修改的状态已经全部满足条件了,显然使用这条边是不优的,直接跳过即可。
然后再来个经典的答案剪枝,就可以飞快草过这道题了(其实本人也没想到直接能过,还以为要再多一点优化才行呢)。
代码:
#include <bits/stdc++.h>
#define rep(i, l, r) for (int i (l); i <= (r); ++ i)
#define rrp(i, l, r) for (int i (r); i >= (l); -- i)
#define pii pair <int, int>
#define eb emplace_back
#define ls p << 1
#define rs ls | 1
using namespace std;
constexpr int N = 1e5 + 5, B = 71, P = 1042702009;
typedef unsigned long long ull;
inline int rd () {
int x = 0, f = 1;
char ch = getchar ();
while (! isdigit (ch)) {
if (ch == '-') f = -1;
ch = getchar ();
}
while (isdigit (ch)) {
x = (x << 1) + (x << 3) + ch - 48;
ch = getchar ();
}
return x * f;
}
int qpow (int x, int y) {
int ret = 1;
for (; y; y >>= 1, x = x * x % P) if (y & 1) ret = ret * x % P;
return ret;
}
int n, m;
int a[7], b[7];
int c[6][6];
int now[1 << 7];
int pre[N];
bool ok[1 << 7], ak[6];
bool g[1 << 7][1 << 7];
class edge {
public:
int u, v, w;
friend bool operator < (const edge &a, const edge &b) {
return a.w < b.w;
}
} e[7 * 7];
int tot, cnt;
int S, T;
int ans = 2e9;
int ct[1 << 7][7];
int h[1 << 7][7];
void dfs (int Now, int ret) {
if (ret >= ans) return ;
if (cnt == (1 << (m + 1)) - 1) {
ans = ret; return ;
}
if (ret + e[Now].w >= ans) return ;
if (Now > tot) return ;
int u = e[Now].u, v = e[Now].v;
bool flag = 0;
rep (i, 0, (1 << m) - 1) {
int s = h[i][u];
bool fl = g[s][now[s]];
flag &= g[s][now[s]];
now[s] |= 1 << v;
if (g[s][now[s]] && ! fl) ++ cnt;
++ ct[s][v];
}
if (! flag) dfs (Now + 1, ret + e[Now].w);
rep (i, 0, (1 << m) - 1) {
int s = h[i][u];
if (! -- ct[s][v]) {
bool fl = g[s][now[s]];
now[s] ^= 1 << v;
if (fl && ! g[s][now[s]]) -- cnt;
}
}
dfs (Now + 1, ret);
}
int32_t main () {
// freopen ("1.in", "r", stdin);
// freopen ("1.out", "w", stdout);
n = rd (), m = rd ();
S = (1 << n) - 1, T = (1 << m) - 1;
-- n, -- m;
int s1 = 0, s2 = 0;
rep (i, 0, n) a[i] = rd (), s1 += a[i];
rep (i, 0, m) b[i] = rd (), s2 += b[i];
if (s1 > s2) return puts ("-1") * 0;
swap (a, b), swap (n, m);
swap (S, T);
rep (s, 0, S) {
rep (t, 0, T) {
int sum = 0;
rep (i, 0, n) if (s >> i & 1) sum += a[i];
rep (j, 0, m) if (t >> j & 1) sum -= b[j];
g[t][s] = sum >= 0;
// cout<<s<<" "<<t<<" "<<g[t][s]<<endl;
}
}
rep (i, 0, (1 << m) - 1) {
rep (u, 0, m) {
h[i][u] = (((i >> u) << 1 | 1) << u) | (i & ((1 << u) - 1));
}
}
rep (i, 0, m) rep (j, 0, n) e[++ tot] = (edge) {i, j, rd ()};
sort (e + 1, e + tot + 1);
dfs (1, 0);
if (ans == 2e9) return puts ("-1") * 0;
cout << ans;
}
还没完呢,毕竟我是来学 hall 定理的。我开始没有想到怎么 dp,因为我以为 dp 时还要用到 hall 定理的性质,然而这题并非如此,这题中的 hall 定理只是将题目转化为了二分图最大匹配问题,而通过 \(a_i,b_i\) 很小可以注意到使用状压 dp,直接设 \(f_{i,S}\) 表示左部已经匹配到了 \(i\),右部每个钥匙还剩多少点未匹配即可进行转移,写个记搜即可~