Loading

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\),右部每个钥匙还剩多少点未匹配即可进行转移,写个记搜即可~

posted @ 2024-11-14 15:23  lalaouye  阅读(2)  评论(0编辑  收藏  举报