[BZOJ3140][HNOI2013]消毒(二分图最小点覆盖)

先考虑一个平面上的问题:
平面上有 n 个点,消除一个xy的矩形里的所有点需要用 min(x,y) 的代价,求消除所有点的最小代价。
在这里,我们可以发现,在这里用 min(x,y) 条竖线或横线就可以覆盖一个 xy 的矩形。这样就变成了二分图最小点覆盖的裸题,套模板即可。

回到问题。同样也可以将问题理解为以下模型:
空间内有 n 个点,每一次操作可以消除一个面上所有的点,求消除所有点的最少操作次数。
但是这是三维的,所以不能简单地求最小点覆盖。怎么做呢?
看到题目中有abc<=5000,也就意味着 a,b,c 中至少有一个不大于 17 。所以就先暴搜对应的轴上的不大于 17 个面是否被操作(对应的面上的所有点被消除),然后求最小点覆盖来更新答案。
同时注意:此题时限卡得较紧,请注意一些常数优化。
代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 5005, E = 23, INF = 0x3f3f3f3f;
int n, D[5], pos, ecnt, nxt[N], adj[N], go[N], val[N], my[N],
X[N][5], Ans, cnt, vis[N], times;
bool sel[E];
void add_edge(int u, int v, int w) {
    nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v; val[ecnt] = w;
}
bool dfs(int u) {
    for (int e = adj[u], v; e; e = nxt[e])
        if (!sel[val[e]] && vis[v = go[e]] < times) {
            vis[v] = times;
            if (!my[v] || dfs(my[v])) {
                my[v] = u;
                return 1;
            }
        }
    return 0;
}
int solve(int tt) {
    int i, j, ans = 0;
    for (i = 1; i <= cnt; i++) my[i] = 0;
    for (i = 1; i <= cnt; i++) {
        times++;
        if (dfs(i)) ans++;
        if (tt + ans >= Ans) return tt + ans;
    }
    return tt + ans;
}
void Dfs(int dep, int tt) {
    if (dep > D[pos]) return (void) (Ans = min(Ans, solve(tt)));
    sel[dep] = 1; Dfs(dep + 1, tt + 1);
    sel[dep] = 0; Dfs(dep + 1, tt);
}
void work() {
    int i, j, k, x; n = 0; Ans = INF; cnt = 0;
    pos = 1; D[1] = read(); D[2] = read(); D[3] = read();
    if (D[2] < D[pos]) pos = 2; if (D[3] < D[pos]) pos = 3;
    for (i = 1; i <= 3; i++) if (i != pos) cnt = max(cnt, D[i]);
    for (i = 1; i <= D[1]; i++) for (j = 1; j <= D[2]; j++)
    for (k = 1; k <= D[3]; k++) {
        x = read(); if (x) X[++n][1] = i, X[n][2] = j, X[n][3] = k;
    }
    ecnt = 0; for (i = 1; i <= cnt; i++) adj[i] = 0;
    for (i = 1; i <= n; i++) {
        if (pos == 1) add_edge(X[i][2], X[i][3], X[i][1]);
        else if (pos == 2) add_edge(X[i][1], X[i][3], X[i][2]);
        else add_edge(X[i][1], X[i][2], X[i][3]);
    }
    printf("%d\n", (Dfs(1, 0), Ans));
}
int main() {
    int T = read();
    while (T--) work();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值