hoj 1086 Don't Get Rooked 二分图匹配

/*

题目:

   象棋中的车在象棋盘上可以放置的最大数目(若没有挡板的话,在同一行或同一列的车

   可以相互攻击,问的是所有的车可以和平地放置的最大数目)

 

分析:

   方法一:

      可以通过暴搜得到答案(题目数据较小)

   方法二:

      可以通过构造二分图的方法来做,以下主要分析二分图的解法

 

方法:

   我们将每一行,每一列被墙隔开,且包含空地的连续区域称作。显然,在一个块之中,

   最多只能放一个车。我们把这些块编上号。

   同样,把竖直方向的块也编上号。

   把每个横向块看作X部的点,竖向块看作Y部的点,若两个块有公共的空地,则在它们之间连边。

   于是,问题转化成这样的一个二部图:

 

   .X..

   ....

   XX..

   ....

 

   我们可以给上面的图形标号(X部):r[i][j]

   1  -1   2   2

   3   3   3   3

   -1 -1   4   4

   5   5   5   5

 

   接着对Y部进行标号(Y部):c[i][j]

   1  -1  5  6

   1   3  5  6

   -1 -1  5  6

   2   4  5  6

   若该位置上不为隔板,则只需对g[r[i][j]][c[i][j]] = true即可构建

   一个二分图,剩下的就是赤裸裸的hungry算法了。

 

   现在转为对数组c和数组r的计算问题,其实我们可以枚举每一个位置上是否为

   空地,若为空地则r[i][j] = n(n为当前的最大标号),每当遇到隔板时,设旗帜

   为真,每当遇到新的空地时,先对n加一,在赋值给r[i][j],列的同理计算。

   另外需注意如此做法的数组g(标记有没有连线)的最大下标会发生变化,即最大值

   会大于4而不会大于164*4

 

*/

//#include <iostream>//发现每次去掉这两句时,内存都会少400k

#include <cstdio>

#include <cstring>

//using namespace std;

#define X 20

int ym[X],c[X][X],r[X][X],n,m;

bool g[X][X],use[X];

bool dfs(int u)          //寻找增广轨算法

{

   int t;

   for(int v=1;v<=m;v++)

      if(g[u][v]&&!use[v])

      {

         use[v] = true;

         t = ym[v];

         ym[v] = u;         //通过取反操作尝试能不能找到增广轨

         if(t==-1||dfs(t))  //能够找到的话

            return true;

         ym[v] = t;         //不能找到的话,付回原值

      }

      return false;

}

int hungry()       //匈牙利算法

{

   int ret = 0;

   memset(ym,-1,sizeof(ym));

   for(int u=1;u<=n;u++)//从顶点u开始看看能不能找到一条增广路

   {

      memset(use,false,sizeof(use));//能找到的话,匹配加1

      if(dfs(u))

         ret++;

   }

   return ret;

}

int main()

{

   freopen("sum.in","r",stdin);

   freopen("sum.out","w",stdout);

   int t;

   char ch[X][X];

   while(scanf("%d",&t),t)

   {

      for(int i=0;i<t;i++)

         scanf("%s",ch[i]);

      memset(g,false,sizeof(g));

      n = m = 0;

      bool flag;

      memset(r,0,sizeof(r));

      for(int i=0;i<t;i++)

      {

         flag = true;

         for(int j=0;j<t;j++)     //算行的标号

         {

            if(ch[i][j]=='.')

            {

                if(flag)        //如果为真,表明前面有隔板,说明标号比前面的大1

                   n++;

                r[i][j] = n;    //赋值

                flag = false;

            }

            else               //设立旗帜

            {

                flag = true;

                r[i][j] = -1;

            }

         }

         flag = true;

         for(int j=0;j<t;j++)  //寻找列的标号

         {

            if(ch[j][i]=='.')

            {

                if(flag)

                   m++;

                flag = false;

                c[j][i] = m;

            }

            else

            {

                flag = true;

                c[j][i] = -1;

            }

         }

      }

      for(int i=0;i<t;i++)

         for(int j=0;j<t;j++)

            if(c[i][j]!=-1&&r[i][j]!=-1)

                g[r[i][j]][c[i][j]] = true;//构造连线

      printf("%d\n",hungry());

   }

   return 0;

}

 

原文地址:https://www.cnblogs.com/yejinru/p/2436393.html