题目描述

在N×M的地图上,X表示草,.表示土地。
一个X与上下左右的X相连形成一片草坪。
现在已知地图上有三片草坪,最少需要将多少个单位上的土地变成草,才能把两块草坪连接成一块草坪。

输入格式

输入第一行为正整数N和M,不超过50。
接下来N行,每行M个字符。

输出格式

输出一个数字表示答案。

输入样例 复制

6 16
................
..XXXX....XXX...
...XXXX....XX...
.XXXX......XXX..
........XXXXX...
..XXX....XXX....

输出样例 复制

4

数据范围与提示

来源:USACO 2011.11
样例解释:

................
..XXXX....XXX...
...XXXX*...XX...
.XXXX..**..XXX..
...*....XXXXX...
..XXX....XXX....

本题第一眼看上去可能没有什么思路,但观察会发现如果在土地中选取一个点,该点到三块草坪的距离和最小那么就可能是连接三块草坪的最小值。但如果三块草坪排列有平行的情况那么就有可能穿过某块草坪得到该点到那块草坪的最小值,这样的结果肯定不是连接三块草坪的最小值。在这时就可以计算出三块草坪两两之间的最短路,然后计算最短的两个最短路相加就是连接三个草坪的最小值。

通过代码:

//一共就两种情况,可能是确定某一个土地到三个草坪和的最小值 
//还有一种就是三个草坪彼此之间的最小值中两个的和
//这两种方法在应用场景之间的互补的,取这两种场景的最小值就行
#include<bits/stdc++.h>
#include <algorithm>
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
using namespace std;

char a[55][55];
int id[55][55];
int n, m;
//构造上下左右移动的数组
int mv[4][2] = {
  {-1,0},{1,0},{0,-1},{0,1}}; 
vector<pair<int, int> > v[4];//存放三种草坪的各个位置 
void dfs(int i, int j, int c) {
	id[i][j] = c;
	v[c].push_back(make_pair(i, j));
	for (int k=0;k<4;k++) {
		int i1 = i+mv[k][0];
		int j1 = j+mv[k][1];
		//考虑越界情况 
		if (i1>n||i1<=0||j1>=m||j1<0) continue;
		//考虑以及出草坪的情况 
		if (a[i1][j1]=='.') continue;
		if (id[i1][j1]) continue;//改点已经被遍历过了 
		//都没有的话直接接着深度优先遍历
		dfs(i1,j1,c); 
	}
}
//计算某一个点到某一个草坪的最小距离 
int Count(int i, int j, int c) {
	int min_len = INT_MAX;
	//遍历i草坪的每一个点,比较每一个点与i,j之间的值,取最小值
	for (int k=0;k<v[c].size();k++) {
		pair<int, int> A = v[c][k];
		min_len = min(min_len, abs(i-A.first)+ abs(j-A.second));
	}
	return min_len;
}

int main() {
	IOS;
	int c_id = 1;
	cin>>n>>m;
	for (int i=1;i<=n;i++) {
		cin>>a[i];
	}
	//对草坪进行分类以及存储. 
	for (int i=1;i<=n;i++) {
		for (int j=0;j<m;j++) {
			if (a[i][j]=='X'&&!id[i][j]) {
				dfs(i, j, c_id++);
			}
		}
	}
	int ans = INT_MAX;//答案 
	//考虑一个点到三个草坪最和短路的问题
	for (int i=1;i<=n;i++) {
		for (int j=0;j<m;j++) {
			if (a[i][j]=='.') {
				ans = min(ans, Count(i,j,1)+Count(i,j,2)+Count(i,j,3)-2);
				
			}
		}
	}
	//该计算彼此草坪之间的最短距离了。
	int min_len[4] = {100,100,100,100};//1:1-2,2:2-3,3:1-3,彼此草坪之间的最短路径 
	//外层循环表示某种草坪
	 for (int i=1;i<=3;i++) {
	 	int j = i==3? 1:i+1;
	 	for (int k=0;k<v[i].size();k++) {
	 		pair<int, int> A = v[i][k];
	 		min_len[i] = min(min_len[i], Count(A.first, A.second, j));
		 }
	}
	//排个序用最小的两个距离作为这种方式的最短路径
	//4、计算连通3个草地的最短距离,找最小值,即情况(2)。并与情况(1)的结果比较。
    for(int i = 1; i <= 3; i++)
        ans = min(ans, min_len[i] + min_len[i+1 <= 3 ? i+1 : 1] - 2);
	cout<<ans;
	return 0;
}