bzoj1458 士兵占领

bzoj1458 士兵占领

有一个 \(M\times N\) 的棋盘,有的格子是障碍。现在你要选择一些格子来放置一些士兵,一个格子里最多可以放置一个士兵,障碍格里不能放置士兵。我们称这些士兵占领了整个棋盘当满足第i行至少放置了 \(L_i\) 个士兵, 第j列至少放置了 \(C_j\) 个士兵。现在你的任务是要求使用最少个数的士兵来占领整个棋盘。

\(M,\ N\leq100\)

网络流,费用流


建出每个为被删除的点 \((i,\ j)\) ,这个点会对 \(tid_i,\ tid_j\) 造成 \(1\) 的贡献。限制每行每列的士兵个数,就把每行每列建成点,连一条边限制容量即可。于是就建出了一个二分图。统计士兵个数可以考虑统计源点流出的容量数,因为每个士兵会流向每行每列两个节点,因此费用会是最终答案的两倍,除以二即可。

对于每行,连边 \((tid_i,\ T,\ L_i,\ 0)\) ,每列同理

对于每个未被删除的点 \((i,\ j)\) ,连边 \((S,\ id,\ \inf,\ 1),\ (id,\ tid_i,\ 1,\ 0),\ (id,\ tid_j,\ 1,\ 0)\)

我的代码交换了 \(M,\ N\) ……

代码

#include <bits/stdc++.h>
using namespace std;

const int maxn = 11000, maxm = 1e5 + 10, inf = 1 << 30;
int N, M, K;
bool isv[105][105], vis[maxn];
int S, T, h[maxn], f[maxn], dis[maxn], pre[maxn];

struct edges {
  int nxt, to, w, c;
  edges(int _n = 0, int _t = 0, int _w = 0, int _c = 0) :
    nxt(_n), to(_t), w(_w), c(_c) {}
} e[maxm];

queue <int> q;

void addline(int u, int v, int w, int c) {
  static int cnt = 1;
  e[++cnt] = edges(h[u], v, w, c), h[u] = cnt;
  e[++cnt] = edges(h[v], u, 0, -c), h[v] = cnt;
}

bool spfa() {
  memset(dis, 0x3f, sizeof dis);
  q.push(S), dis[S] = 0, f[S] = inf;
  while (!q.empty()) {
    int u = q.front();
    vis[u] = 0, q.pop();
    for (int i = h[u]; i; i = e[i].nxt) {
      int v = e[i].to;
      if (e[i].w && dis[u] + e[i].c < dis[v]) {
        pre[v] = i;
        f[v] = min(f[u], e[i].w);
        dis[v] = dis[u] + e[i].c;
        if (!vis[v]) q.push(v), vis[v] = 1;
      }
    }
  }
  return dis[T] < 1e9;
}

void EK() {
  int res = 0, tmp = 0;
  while (spfa()) {
    tmp += f[T], res += f[T] * dis[T];
    for (int u = T; u != S; u = e[pre[u] ^ 1].to) {
      e[pre[u]].w -= f[T], e[pre[u] ^ 1].w += f[T];
    }
  }
  printf("%d", res >> 1);
}

int main() {
  int sr[105], sc[105];
  scanf("%d %d %d", &N, &M, &K);
  S = N * M + N + M + 1, T = S + 1;
  for (int i = 1; i <= N; i++) {
    scanf("%d", sr + i);
    addline(N * M + i, T, sr[i], 0);
  }
  for (int i = 1; i <= M; i++) {
    scanf("%d", sc + i);
    addline(N * M + N + i, T, sc[i], 0);
  }
  for (int i = 1, x, y; i <= K; i++) {
    scanf("%d %d", &x, &y), isv[x][y] = 1;
  }
  int R[105], C[105];
  memset(R, 0, sizeof R);
  memset(C, 0, sizeof C);
  for (int i = 1; i <= N; i++) {
    for (int j = 1; j <= M; j++) {
      if (!isv[i][j]) {
        R[i]++, C[j]++;
        int tid = M * (i - 1) + j;
        addline(S, tid, inf, 1);
        addline(tid, N * M + i, 1, 0);
        addline(tid, N * M + N + j, 1, 0);
      }
    }
  }
  for (int i = 1; i <= N; i++) {
    if (R[i] < sr[i]) return puts("JIONG!"), 0;
  }
  for (int i = 1; i <= M; i++) {
    if (C[i] < sc[i]) return puts("JIONG!"), 0;
  }
  EK();
  return 0;
}

转载于:https://www.cnblogs.com/Juanzhang/p/10731448.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值