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;

 

}

 

 

 

posted @ 2012-04-07 19:33  yejinru  阅读(243)  评论(0编辑  收藏  举报