【二分圖匹配】宮廷守衛

【问题描述】
	 从前有一个王国,这个王国的城堡是一个矩形,被分为M×N个方格。一些方格是墙,而另一些是空地。这个王国的国王在城堡里设了一些陷阱,每个陷阱占据一块空地。
    一天,国王决定在城堡里布置守卫,他希望安排尽量多的守卫。守卫们都是经过严格训练的,所以一旦他们发现同行或同列中有人的话,他们立即向那人射击。因此,国王希望能够合理地布置守卫,使他们互相之间不能看见,这样他们就不可能互相射击了。守卫们只能被布置在空地上,不能被布置在陷阱或墙上,且一块空地只能布置一个守卫。如果两个守卫在同一行或同一列,并且他们之间没有墙的话,他们就能互相看见。(守卫就像象棋里的车一样)
    你的任务是写一个程序,根据给定的城堡,计算最多可布置多少个守卫,并设计出布置的方案。
【输入】
	第一行两个整数M和N(1≤M,N≤200),表示城堡的规模。
	接下来M行N列的整数,描述的是城堡的地形。第i行j列的数用ai,j表示。
	ai,j=0,表示方格[i,j]是一块空地;
	ai,j=1,表示方格[i,j]是一个陷阱;
	ai,j=2,表示方格[i,j]是墙。
【输出】
	第一行一个整数K,表示最多可布置K个守卫。
	此后K行,每行两个整数xi和yi,描述一个守卫的位置。
【样例】
guards.in
3 4
2 0 0 0
2 2 2 1
0 1 0 2
guards.out
2
1 2
3 3
 样例数据如图(黑色方格为墙,白色方格为空地,圆圈为陷阱,G表示守卫)

                                              


這是一道二分圖最大匹配。

首先建模,將被牆隔開的各個橫塊,作為二分圖的左半集;
再將被牆隔開的各個豎塊,作為二分圖的右半集;
若一個橫塊跟一個豎塊的交點為空地,則將它們連一條邊。

然後求一個最大匹配,輸出所有匹配就行了。

Accode:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <bitset>

using std::bitset;

const char fi[] = "guards.in";
const char fo[] = "guards.out";
const int maxN = 40010;
const int maxR = 210;
const int MAX = 0x3fffff00;
const int MIN = -MAX;

struct Edge {int dest; Edge *next; };
Edge *edge[maxN];
bitset <maxN> marked;
int hh[maxR][maxR], lx[maxR][maxR];
int map[maxR][maxR];
int Link[maxN];
int x[maxN], y[maxN];
int N, M, n, m, ans;

  void init_file()
  {
    freopen(fi, "r", stdin);
    freopen(fo, "w", stdout);
  }

  inline void insert(int u, int v)
  {
    Edge *p = new Edge;
    p -> dest = v;
    p -> next = edge[u];
    edge[u] = p;
  }

  void readdata()
  {
    scanf("%d%d", &N, &M);
    for (int i = 1; i < N + 1; ++i)
     for (int j = 1; j < M + 1; ++j)
      scanf("%d", &map[i][j]);
    for (int i = 1; i < N + 1; ++i)
     for (int j = 1; j < M + 1; ++j)
      if (map[i][j] != 2)
      {
        if (!hh[i][j])
        {
          x[++n] = i; //記錄該橫塊的橫座標。
          hh[i][j] = n; //記錄(i, j)所屬的橫塊。
          for (int k = j + 1; k < M + 1; ++k)
          {
            if (map[i][k] == 2) break;
            hh[i][k] = n;
          } //向右找與此位置相連的區域。
          for (int k = j - 1; k; --k)
          {
            if (map[i][k] == 2) break;
            hh[i][k] = n;
          } //向左找與此位置相連的區域。
        }
        if (!lx[i][j])
        {
          y[++m] = j; //記錄該豎塊的縱座標。
          lx[i][j] = m; //記錄(i, j)所屬的豎塊。
          for (int k = i + 1; k < N + 1; ++k)
          {
            if (map[k][j] == 2) break;
            lx[k][j] = m;
          } //向下找與此位置相連的區域。
          for (int k = i - 1; k; --k)
          {
            if (map[k][j] == 2) break;
            lx[k][j] = m;
          } //向上找與此位置相連的區域。
        }
        if (!map[i][j])
          insert(hh[i][j], lx[i][j]);
	//(i, j)一定要為空地才能連邊。
      }
  }

  bool Find(int u)
  {
    for (Edge *p = edge[u]; p; p = p -> next)
    {
      int v = p -> dest;
      if (!marked.test(v))
      {
        marked.set(v);
        if (!Link[v] || Find(Link[v]))
          {Link[v] = u; return true; }
      }
    }
    return false;
  }

  void work()
  {
    for (int i = 1; i < n + 1; ++i)
      {marked.reset(); if (Find(i)) ++ans; }
    printf("%d\n", ans);
    for (int j = 1; j < m + 1; ++j)
     if (Link[j])
      printf("%d %d\n", x[Link[j]], y[j]);
  }

int main()
{
  init_file();
  readdata();
  work();
  exit(0);
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值