匈牙利算法 zoj1654

资料:趣味理解

           byvoid大牛

首先二分图有两组顶点,一组记X,一组记Y,所有Xi之间无边,所有Yi之间无边。

交错路:对于给定的一个匹配,交错路是一条路径,该路径上相邻边满足,一条属于匹配M,另一条不属于匹配M

未盖点:没有被匹配M覆盖的点

增广路:交错路两端是未盖点

匈牙利算法是从一组点开始,比如从X开始吧。

bool dfs(int u){
    for(int v=1;v<=y;v++){
        if(adj[u][v]&&!vis[v]){
            vis[v]=1;
            if(match[v]==-1||dfs(match[v])){
                match[v]=u;
                return true;
            }
        }
    }
    return false;
}

我们来看dfs,dfs的返回值是布尔型,dfs递归下去,最底层返回true是什么情况?

可以发现是match[v]==-1,因为如果是dfs(match[v])的话,说明还要继续递归下去,并不是最底层。那match[v]==-1表示什么呢?这个表示v还是未盖点,也就是dfs在寻找交错路时,最后一个点是未盖点。

而X组必然也是未盖点,也就是dfs返回true就说明找到一条增广路。

其实dfs的过程是这样的,首先从Xi遍历所有邻接点,如果邻接点Yi是未盖点,那很显然是最简单的增广路(两个未盖点有一条连边),若邻接点Yi是盖点,那么必然存在Xj=match[Yi],满足<Yi,Xj>属于匹配。那么我们就从

Xj继续往下找,也就是dfs(match[Yi]),其实这么一层,我们发现交错路已经有两条边,<Xi,Yi>,<Yi,Xj>,前者不属于匹配,后者属于匹配;这里有一个问题,继续从Xj dfs下去,会不会找到Yj,然后<Xj,Yj>属于匹配呢?

这显然不可能,因为原先<Yi,Xj>属于匹配而如果<Xj,Yj>属于匹配的话,这显然与匹配的定义矛盾,匹配也叫边独立集(是不能有多条边覆盖同一点的),因此从Xj出发,可以放心下一条必然是不属于匹配的边,这恰好和前一层接上,如此循环不停交错,加上dfs最后结束是一条不属于匹配的边,完美地找到了一条增广路。

如果细心地话,你会留意我刚才的有一句话,X组必然也是未盖点,这个其实是有条件的,我们必须保证初始时图是没有匹配的,我们可以用数学归纳法证明dfs(Xi)只会用到[X1...Xi]之间的点,当然for循环必须是从1开始。

如果原先图是有匹配的,那么我们在dfs前加一句判断该Xi是否是未盖点就可以了。可以采用开两个match数组,一个表示X,一个表示Y或者X和Y的点的id不要重复,这样一个match数组就可以了。

但是一般初始图是没有匹配的,所以不需要考虑这个问题。

int hungary(){
    memset(match,-1,sizeof match);
    int ans=0;
    for(int i=1;i<=x;i++){
        memset(vis,0,sizeof vis);
        if(dfs(i)) ans++;
    }
    return ans;
}

附上一经典的题目:zoj1654

Place the Robots Time Limit: 5 Seconds       Memory Limit: 32768 KB

Robert is a famous engineer. One day he was given a task by his boss. The background of the task was the following:

Given a map consisting of square blocks. There were three kinds of blocks: Wall, Grass, and Empty. His boss wanted to place as many robots as possible in the map. Each robot held a laser weapon which could shoot to four directions (north, east, south, west) simultaneously. A robot had to stay at the block where it was initially placed all the time and to keep firing all the time. The laser beams certainly could pass the grid of Grass, but could not pass the grid of Wall. A robot could only be placed in an Empty block. Surely the boss would not want to see one robot hurting another. In other words, two robots must not be placed in one line (horizontally or vertically) unless there is a Wall between them.

Now that you are such a smart programmer and one of Robert's best friends, He is asking you to help him solving this problem. That is, given the description of a map, compute the maximum number of robots that can be placed in the map.


Input


The first line contains an integer T (<= 11) which is the number of test cases. 

For each test case, the first line contains two integers m and n (1<= m, n <=50) which are the row and column sizes of the map. Then m lines follow, each contains n characters of '#', '*', or 'o' which represent Wall, Grass, and Empty, respectively.


Output

For each test case, first output the case number in one line, in the format: "Case :id" where id is the test case number, counting from 1. In the second line just output the maximum number of robots that can be placed in that map.


Sample Input

2
4 4
o***
*###
oo#o
***o
4 4
#ooo
o#oo
oo#o
***#


Sample Output

Case :1
3
Case :2
5

建模就看这个博客:题解

为什么这样做是对的,我来说说自己的见解:

首先X表示行,每个X只能放一个,但是在Xi和Xj上怎么放才能防止冲突呢?首先在行方向必然是不可能冲突的,但是在列方向可能冲突,比如选择的Xi这连续块中某一格子的纵坐标是1,而选择的Xj这连续块中某一格子的纵坐标也是1,而恰好在1列方向,这两个格子冲突,这种情况如何避免?

从建模方法来看,这两个冲突的格子,必然对应了一组竖直方向连续块,比如Yi,而Xi,Xj与Yi均有一个交点,

在二分图上表现为Xi与Yi有边,Xj与Yi也有边,所以我们不能同时选择这些有交点(Yi)的边,而根据匹配的定义,恰好不可以有交点,那么最大匹配当然对应最大的可行方案。

此题构思巧妙,值得反复琢磨和推敲!

注意最坏情况是:50*50,'o'和'#'相间隔的情况,最多1250个点,数组开大点,否则要RE。

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#define Maxn 1260
using namespace std;

int adj[Maxn][Maxn];
int match[Maxn];
int vis[Maxn];
int x,y;
bool dfs(int u){
    for(int v=1;v<=y;v++){
        if(adj[u][v]&&!vis[v]){
            vis[v]=1;
            if(match[v]==-1||dfs(match[v])){
                match[v]=u;
                return true;
            }
        }
    }
    return false;
}
int hungary(){
    memset(match,-1,sizeof match);
    int ans=0;
    for(int i=1;i<=x;i++){
        memset(vis,0,sizeof vis);
        if(dfs(i)) ans++;
    }
    return ans;
}
char mat[Maxn][Maxn];
int r[Maxn][Maxn],c[Maxn][Maxn];
int main()
{
    int t,m,n,cas=1;
    cin>>t;
    while(t--){
        memset(r,0,sizeof r);
        memset(c,0,sizeof c);
        printf("Case :%d\n",cas++);
        cin>>m>>n;
        for(int i=1;i<=m;i++)
            for(int j=1;j<=n;j++)
                cin>>mat[i][j];
        int tot=0;
        for(int i=1;i<=m;i++){
            bool flag=true;
            for(int j=1;j<=n;j++)
                if(mat[i][j]=='o'){
                    if(flag){
                        r[i][j]=++tot;
                        flag=false;
                    }
                    else r[i][j]=tot;
                }
                else if(mat[i][j]=='#')
                    flag=true;
        }
        x=tot,tot=0;
        for(int j=1;j<=n;j++){
            bool flag=true;
            for(int i=1;i<=m;i++)
                if(mat[i][j]=='o'){
                    if(flag){
                        c[i][j]=++tot;
                        flag=false;
                    }
                    else c[i][j]=tot;
                }
                else if(mat[i][j]=='#')
                    flag=true;
        }
        y=tot;
        memset(adj,0,sizeof adj);
        for(int i=1;i<=m;i++)
            for(int j=1;j<=n;j++)
                if(mat[i][j]=='o')
                    adj[r[i][j]][c[i][j]]=1;
        printf("%d\n",hungary());
    }
	return 0;
}


你可能感兴趣的:(匈牙利算法 zoj1654)