试题 I: 估计人数
时间限制: 1.0s 内存限制: 512.0MB 本题总分:25 分
【问题描述】
给定一个 N × M 的方格矩阵,矩阵中每个方格标记 0 或者 1 代表这个方格
是不是有人踩过。
已知一个人可能从任意方格开始,之后每一步只能向右或者向下走一格。
走了若干步之后,这个人可以离开矩阵。这个人经过的方格都会被标记为 1,
包括开始和结束的方格。注意开始和结束的方格不需要一定在矩阵边缘。
请你计算至少有多少人在矩阵上走过。
【输入格式】
输入第一行包含两个整数 N、M。
以下 N 行每行包含 M 个整数 (0/1),代表方格矩阵。
【输出格式】
输出一个整数代表答案。
【样例输入】
5 5
00100
11111
00100
11111
00100
【样例输出】
3【数据规模与约定】
对于所有评测用例,1 ≤ N, M ≤ 20,标记为 1 的方格不超过 200 个。
诶,明天就该国赛了,今天让这道题浪费一天,不过收获很大,这个题对于没学过算法的我比较难,还好在网上找到了相关资料,琢磨一下午,刚刚写出来。
思路:
这是一道典型的“可相交的最小路径覆盖”问题。
大概解法:
(1)先把 “可相交的最小路径覆盖” 问题转化成 “不相交的最小路径覆盖” 问题。
(可以通过弗洛伊德算法转换)
(2)然后求出“不相交的最小路径覆盖”问题的最小路径数
(最小路径数=图中节点总数-节点最大匹配度)
(3)求(2)的重点是求出节点最大匹配度。而节点最大匹配度可以通过匈利亚算法求出。
(4)打完收工。
由于时间关系,暂时就不很细致的讲解了,给出参考文章
参考大佬文章,通俗易懂。
1、 “可相交的最小路径覆盖” 和“不相交的最小路径覆盖”的概念:https://blog.csdn.net/qq_39627843/article/details/82012572
2:、弗洛伊德算法:https://www.cnblogs.com/wangyuliang/p/9216365.html
3、匈利亚算法:https://blog.csdn.net/Arabic1666/article/details/79824390
简单看完这三篇文章,有了概念之后,根据上方解法过程,结合我的代码和注释,很容易搞定。
代码:
import java.util.Scanner;
public class Main {
/* 可相交的最小路径覆盖 */
// n行m列
int n, m;
//地图
char[][] matrix;
//地图有多少个可以走的点
int pointcount = 0;
//连接表,记录每个点到任意一个点是否走到,构成一个二维矩阵
boolean[][] con;
//给每一个点标上序号,第一个点序号为1,跟地图位置对应
//功能:作为索引用于转换 连接表con
int[][] num;
/* 匈牙利算法的工具 */
//匹配表,存放匹配关系,如:一个点a匹配另一个点b,但b有被其他点匹配过,都在这个表中对应
int[] matchtable;
//点b是否被匹配过
boolean[] ismatch;
public Main() {
Scanner sn = new Scanner(System.in);
n = sn.nextInt();
m = sn.nextInt();
matrix = new char[n][m];
num=new int[n][m];
sn.nextLine();
//读取地图
for (int i = 0; i < n; i++) {
matrix[i] = sn.nextLine().trim().toCharArray();
for (int j = 0; j < m; j++) {
//记录序号,从1开始的
if (matrix[i][j] == '1')num[i][j]=++pointcount;
}
}
//设置连接表大小,0位置不能用,因此需要pointcount+1的行和列
con=new boolean[pointcount+1][pointcount+1];
//根据序号开始转换连接表
for(int i=0;i<n-1;i++){
for(int j=0;j<m-1;j++){
//如果可以向下走或者向右走,说明两个点之间存在直接连接关系,对应到con上
if(matrix[i][j]=='1'&&matrix[i][j+1]=='1')
con[num[i][j]][num[i][j+1]]=true;
if(matrix[i][j]=='1'&&matrix[i+1][j]=='1')
con[num[i][j]][num[i+1][j]]=true;
}
}
/* 弗洛伊德算法 */
/* 以k为中间点,遍历所有点是否能够使得con表中原本不能直接相连的点a和点b
* 但通过k点,可以将a和b连接起来。
* 比如k在a的右边,b在k的下边,那通过k就可以使得a,b连接
*/
for(int k=1;k<=pointcount;k++){
for(int i=1;i<=pointcount;i++){
for(int j=1;j<=pointcount;j++){
con[i][j]|=(con[i][k]&&con[k][j]);
}
}
}
/* 匈牙利算法 */
//开始统计匹配度
//初始化工具
matchtable=new int[pointcount+1];
int count=0;
//统计匹配度
//查看有哪些点可以匹配
for(int i=1;i<=pointcount;i++) {
ismatch=new boolean[pointcount+1];//每次都初始化一次
if(dfs(i)) count++;
}
//最小路径覆盖=原图的结点数- 所有点根据con找到的最大匹配数
System.out.println(pointcount-count);
}
/* 匈牙利算法核心 */
//点a是否能够匹配到一个合适的点b,
//判断标准就是con中两个点是否能连接
//一个点可能会有很多个点连接
//匈奴利亚算法的功能就是达到最大匹配度
public boolean dfs(int i) {
for(int j=1;j<=pointcount;j++) {
if(!con[i][j])continue;//i和j无连接,跳过
if(!ismatch[j]) {
ismatch[j]=true;
/* 如果在点i之前没有点与点j匹配,直接返回true,匹配成功
* 如果点j已有匹配的点k,
* 但点k还能找到其他点匹配。
* 那就赶走k点,取而代之,将点i与点j匹配
* 点k继续上述过程。
* 如果k的结果返回true。点k找到新的,说明点i可以与点j匹配,匹配成功
* 如果k的结果返回false,说明点i挤走点k,点k无法找到可以匹配的其他点
* 那对我们来说,匹配度是不变的,直接结束,不让i代替k。
*/
if(matchtable[j]==0||dfs(matchtable[j])) {
matchtable[j]=i;
return true;
}
}
}
return false;
}
public static void main(String[] args) {
new Main();
}
}