HDU 1281解题报告

来源:互联网 发布:家庭千兆网络组建方案 编辑:程序博客网 时间:2024/05/16 10:02

棋盘游戏

                                    Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
                                                              Total Submission(s): 2651    Accepted Submission(s): 1547


Problem Description
小希和Gardon在玩一个游戏:对一个N*M的棋盘,在格子里放尽量多的一些国际象棋里面的“车”,并且使得他们不能互相攻击,这当然很简单,但是Gardon限制了只有某些格子才可以放,小希还是很轻松的解决了这个问题(见下图)注意不能放车的地方不影响车的互相攻击。 
所以现在Gardon想让小希来解决一个更难的问题,在保证尽量多的“车”的前提下,棋盘里有些格子是可以避开的,也就是说,不在这些格子上放车,也可以保证尽量多的“车”被放下。但是某些格子若不放子,就无法保证放尽量多的“车”,这样的格子被称做重要点。Gardon想让小希算出有多少个这样的重要点,你能解决这个问题么?
 

Input
输入包含多组数据, 
第一行有三个数N、M、K(1<N,M<=100 1<K<=N*M),表示了棋盘的高、宽,以及可以放“车”的格子数目。接下来的K行描述了所有格子的信息:每行两个数X和Y,表示了这个格子在棋盘中的位置。
 

Output
对输入的每组数据,按照如下格式输出: 
Board T have C important blanks for L chessmen.
 

Sample Input
3 3 41 21 32 12 23 3 41 21 32 13 2
 

Sample Output
Board 1 have 0 important blanks for 2 chessmen.Board 2 have 3 important blanks for 3 chessmen.
 

Author
Gardon
 

Source
杭电ACM集训队训练赛(VI)
 

Recommend
lcy   |   We have carefully selected several similar problems for you:  1507 1528 1498 2063 2444 

           这道题比较坑,也比较难。题目中的重点边的定义也不甚清晰,可能让人难以理解出题人的意思。

            下面引用别人对本题的解读,我觉得本题还是属于比较难的题目。

             这题可以看成行与列的二分匹配问题,因为每行每列至多只能放一个棋子。第i行与j列匹配代表棋盘第i行j列这个位置放棋子。那么,棋盘上的点就是二分图的边;“车”的个数就是二分图的最大匹配数。题目的关键是求重要点。现假设最大匹配数为ans,且已经求出某一种匹配策略。

1 :枚举所有可以放的点,去掉某一点后(这里的点指棋盘上的点,也就是二分图的边),就得到一个新的二分图了

if  (新二分图的最大匹配数 == ans

                 then这个点不是重要点

else // 即新的二分图达不到ans这个匹配数,那么这个点就是必须放的,否则达不到ans。-->重要边

          then计数+1

2 :但是这样枚举效率太低。实际上,删边只需考虑求出的匹配边(因为删除非匹配边得到的匹配数不变)。这样,只需删除ans条边,复杂度就降低了。

  再进一步分析,删除一条边以后,没有必要重新求删边后新的二分图的最大匹配,只需检查删边后的匹配中--->可不可以再找到新的增广链就可以了。这样,时间复杂度就进一步降到了。

3 : 这样的优化是不可取的:

   在判断是否存在增广路得时候,不能只以删除的匹配边的顶点作起点来找增广路

   正确的方法是:以删边后新的二分图的所有未匹配顶点出发做增广,都找不到增广路,匹配不能再增加


   总结本题,需要对二分图最大匹配有深刻的认识。二分图最大匹配必须要重点注意的是:如果不存在增广路,即为最大匹配。删掉一条边以后,如果还能找到增广路,那么就能够达到原有的匹配数。因为找到增广路以后对增广路取反以后,边数加1。如果从剩下未匹配的边开始无法找到增广路,那么就说明删边操作后,新的二分图无法达到原有的匹配数。这是二分图最大匹配最实质性的东西。必须深入思考后才能够理解该删边操作寻找增广路做法的合理性。


      下面是参考代码:

       

#include<cstdio>#include<iostream>#include<algorithm>#include<vector>#include<cstring>#define pb push_back#define CLR(x) memset(x,0,sizeof(x))#define __CLR(x) memset(x,-1,sizeof(x))using namespace std;int n,m,k;int mx[110],my[110];bool vis[110];int g[110][110];bool dfs(int u,bool &flag)      //引入flag是为了保证在进行删边操作时不改变匹配的对应值{    for(int i=1; i<=m; i++)    {        if(g[u][i]&&!vis[i])        {            vis[i]=1;            if(my[i]==-1||dfs(my[i],flag))            {                if(flag)                {                    my[i]=u;                    mx[u]=i;                }                return true;            }        }    }    return false;}int main(){    int cas=1;    while(~scanf("%d%d%d",&n,&m,&k))    {        CLR(g);        for(int i=1; i<=k; i++)        {            int x,y;            scanf("%d%d",&x,&y);            g[x][y]=1;        }        __CLR(mx);        __CLR(my);        int num=0;        for(int i=1; i<=n; i++)        {            if(mx[i]==-1)            {                CLR(vis);                bool f=true;                if(dfs(i,f))                    num++;            }        }        int ans=0;        for(int i=1; i<=n; i++)        {            if(mx[i]!=-1)        //删除匹配边            {                int t=mx[i];                mx[i]=-1,my[t]=-1;                g[i][t]=0;                bool flag=0;                for(int j=1; j<=n; j++)                {                    if(mx[j]==-1)    //对未匹配点进行搜索                    {                        CLR(vis);                        bool f=false;                        if(dfs(j,f))                        {                            flag=1;                            break;                        }                    }                }                if(!flag)                    ans++;                mx[i]=t;          //对匹配边进行恢复                my[t]=i;                g[i][t]=1;            }        }        printf("Board %d have %d important blanks for %d chessmen.\n",cas++,ans,num);    }}

0 0