现在的位置: 首页 > 综合 > 正文

ZOJ–1654–Place the Robots【二分图最大匹配】

2018年01月12日 ⁄ 综合 ⁄ 共 3521字 ⁄ 字号 评论关闭

链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=654

题意:Robert是一个著名的工程师。一天,他的老板给他分配了一个任务。任务的背景是:给定一个 m×n 大小的地图,地图由方格组成,在地图中有 3 种方格-墙、草地和空地,他的老板希望能在地图中放置尽可能多的机器人。每个机器人都配备了激光枪,可以同时向四个方向(上、下、左、右)开枪。机器人一直待在最初始放置的方格处,不可移动,然后一直朝四个方向开枪。激光枪发射出的激光可以穿透草地,但不能穿透墙壁。机器人只能放置在空地。当然,老板不希望机器人互相攻击,也就是说,两个机器人不能放在同一行(水平或垂直)
,除非他们之间有一堵墙格开。 给定一张地图,你的程序需要输出在该地图中可以放置的机器人的最大数目。

思路:没有太好的思路,图论书上的思路很好,直接粘上来吧。

样例输入中两个测试数据所描述的地图如图 7.23(a)和(b)所示。在图(a)中,最多可以放置 4个机器人,一种放置方案是在(0, 0)、(2, 1)、(2, 3)和(3, 4)四个位置(行、列位置序号从0开始计起)上放置机器人。在图(b)中,最多可以放置3个机器人,一种放置方案是在(0, 0)、(2, 1)和(2, 3)三个位置上放置机器人。 
在问题的原型中,草地,墙这些信息不是本题所关心的,本题关心的只是空地和空地之间的联系。因此,很自然想到了下面这种简单的模型:以空地为顶点,在有冲突的空地间连边。 

这样对图 7.23(a)所示的地图,把所有的空地用数字标明,如图 7.24(a)所示;把所有有冲突的空地间用边连接后得到图(b)。于是,问题转化为求图的最大独立集问题:求最大顶点集合,集合中所有顶点互不连接(即互不冲突) 。但是最大点独立集问题是一个NP问题,没有有效的算法能求解(复杂度O(2^n))。 

将每一行被墙隔开、且包含空地的连续区域称作“块” 。显然,在一个块之中,最多只能放一个机器人。把这些块编上号,如图7.25(a)所示。需要说明的是,最后一行,即第4行有两个空地,但这两个空地之间没有墙壁,只有草地,所以这两个空地应该属于同一“块” 。 同样,把竖直方向的块也编上号,如图7.25(b)所示。

把每个横向块看作二部图中顶点集合 X 中的顶点,竖向块看作集合 Y 中的顶点,若两个块有公共的空地(注意,每两个块最多有一个公共空地) ,则在它们之间连边。例如,横向块2和竖向块1有公共的空地,即(2, 0),于是在X集合中的顶点2和Y集合中的顶点1之间有一条边。这样,问题转化成一个二部图,如图7.25(c)所示。 

由于每条边表示一个空地(即一个横向块和一个竖向块的公共空地) ,有冲突的空地之间必有公共顶点。例如边(x1, y1)表示空地(0, 0)、边(x2, y1)表示空地(2, 0),在这两个空地上不能同时放置机器人。所以问题转化为在二部图中找没有公共顶点的最大边集,这就是最大匹配问题。 

接下来以 7.23(a)所示的地图为例解释二部图的构造过程:在下面的代码中,二维数组 xs 和ys 分别用来给水平方向和垂直方向上“块”进行编号,编号的结果如图 7.26(b)、(c)所示;二维数组 g 用来对水平方向上和垂直方向上的块进行连接,若两个块有公共的空地,则在它们之间连边,如果g[i][j]==1,那么水平方向上的第i个块跟垂直方向上的第j个块有公共的空地,要连边;连边后的结果如图(d)所示;这样构造的二部图如图(e)所示。

如果从0匹配开始增广,求得的最大匹配如图7.27(a)所示,粗线边代表匹配中的边。 
假设从给定的一个初始匹配(如图7.27(b)所示)出发进行增广(可以通过在求最大匹配前给x、y 数组赋值来实现) ,其过程为:从顶点x1出发寻找可增广路,它的邻接顶点y1已经匹配了,所以从y1匹配顶点即x2出发递归寻找可增广路,x2的邻接顶点y2没有匹配,所以找到一条可增广路( x1, y1, x2, y2 );沿着这条可增广路,按照匈牙利算法可以使得当前匹配增加1;改进后的匹配就是最大匹配了。

(以上内容摘自《图论算法理论、实现及应用 》 王桂平、王  衍、任嘉辰 编著)

讲解虽然有点长,但是很详细,我按照书上的思路,用邻接表和Hopcroft-Karp增广,果断MLE了。。如果极端情况添加的边很多,它是一个稠密图,邻接表里指针和域两个int值,比一个邻接矩阵可能更占内存,挺好的题ZOJ居然只给32M内存。。给64M就能用邻接表写了,我刚开始忽略了邻接表里两个int,以为是Hopcroft-Karp增广多开的数组超内存了,就改用和书上一样的DFS增广了

#include<cstring>
#include<string>
#include<fstream>
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cctype>
#include<algorithm>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<stack>
#include<ctime>
#include<cstdlib>
#include<functional>
#include<cmath>
using namespace std;
#define PI acos(-1.0)
#define MAXN 2001000
#define eps 1e-7
#define INF 0x7FFFFFFF
#define LLINF 0x7FFFFFFFFFFFFFFF
#define seed 1313131
#define MOD 1000000007
#define ll long long
#define ull unsigned ll
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1

int vis[2510];
int cnt,n1,n2;  //n1水平块数,n2垂直块数
int mx[2510],my[2510];    //mx最大匹配中与左边点i相连的边,my最大匹配中与右边点i相连的边
int xs[60][60],ys[60][60],g[2510][2510];
bool Find(int u){
    int i,j;
    for(i=1;i<=n2;i++){
        if(!vis[i] && g[u][i]){
            vis[i] = 1;
            if(!my[i]||Find(my[i])){
                mx[u] = i;
                my[i] = u;
                return true;
            }
        }
    }
    return false;
}
int matching(){
    int i,j;
    memset(mx,0,sizeof(mx));
    memset(my,0,sizeof(my));
    int ans = 0;
    for(i=1;i<=n1;i++){
        if(!mx[i]){
            memset(vis,0,sizeof(vis));
            if(Find(i)) ans++;
        }
    }
    return ans;
}
char mapp[60][60];
int main(){
    int i,j,t,cas=1,m,n;
    int number;
    int flag;
    scanf("%d",&t);
    while(t--){
        memset(xs,0,sizeof(xs));
        memset(ys,0,sizeof(ys));
        scanf("%d%d",&m,&n);
        for(i=0;i<m;i++)    scanf("%s",mapp[i]);
        number = 0;
        for(i=0;i<m;i++){
            flag = 0;
            for(j=0;j<n;j++){
                if(mapp[i][j]=='o'){
                    if(!flag)   number++;
                    xs[i][j] = number;
                    flag = 1;
                }
                else if(mapp[i][j]=='#')    flag = 0;
            }
        }
        n1 = number;
        number = 0;
        for(j=0;j<n;j++){
            flag = 0;
            for(i=0;i<m;i++){
                if(mapp[i][j]=='o'){
                    if(!flag)   number++;
                    ys[i][j] = number;
                    flag = 1;
                }
                else if(mapp[i][j]=='#')    flag = 0;
            }
        }
        n2 = number;
        memset(g,0,sizeof(g));
        for(i=0;i<m;i++){
            for(j=0;j<n;j++){
                if(xs[i][j])    g[xs[i][j]][ys[i][j]] = 1;
            }
        }
        printf("Case :%d\n",cas++);
        int ans = matching();
        printf("%d\n",ans);
    }
    return 0;
}

抱歉!评论已关闭.