最少连通代价(mincon.cpp)
Description
在一个 N 行 M 列的字符网格上,恰好有 2 个彼此分开的连通块。每个连通块的一个格点与它的上、下、左、右的格子连通。如下图所示:
现在要把这 2 个连通块连通,求最少需要把几个’.’转变成’X’。上图的例子中, 最少只需要把 3 个’.’转变成’X’。下图用’*’表示转化为’X’的格点。
Input
第 1 行:2 个整数 N 和 M(1<=N,M<=50)
接下来 N 行,每行 M 个字符,’X’表示属于某个连通块的格点,’.’表示不属于某个连通块的格点
Output
第 1 行:1 个整数,表示最少需要把几个’.’转变成’X’
Sample Input
6 16
................
..XXXX....XXX...
...XXXX....XX...
.XXXX......XXX..
........XXXXX...
.........XXX....
Sample Output
3
题目解析
这道题是一道搜索题,或者说是一道“半搜索题”。作者的做法和其他同学的做法有些出入,先讲其他同学的做法吧。
他们的做法是纯搜索,并且深度优先搜索和广度优先搜索都用到了,这也应该是大多数同学们的想法。
首先是输入。输入的部分可以采取优化,由于不需要在输入时对某一元素进行单独操作,所以我们没有必要用“%c”一个字符一个字符地读入,而是直接用“%s”读入一整行,还省去了对换行符的处理。但是对于喜欢从1开始计数的同学来说,这个方法似乎不可行,实际上“%s”也可以从1开始读入字符串的,即scanf("%s",s+1);
。
题目中明显给出了输入数据包含两个连通块,但是并没有用任何方式标明某个块是属于哪一个连通块。于是输入后的第一个处理便是——用字符区分出两个连通块。我们可以像城堡问题(参考:OpenJudge 1817城堡问题)一样查找连通块,并作标记。其实并不用把两个连通块都处理成其他字符,我们只需要让空白、连通块1和连通块2有区别就行了,也就是只需要处理1个连通块。
然后再用2重for循环遍历整个矩阵,一旦找到一个属于连通块1的元素就从那个位置开始进行广度优先搜索,目的是找到离该位置最近的属于连通块2的位置的距离。答案用ans储存。
看明白了他们的代码的作者想问——一个深度优先搜索加上不知多少个广度优先搜索真的不会超时吗?
还是看看作者的做法吧。作者之所以说“有很大出入”是因为我的代码除了输入,与其他同学的几乎完全不一样。首先作者也需要处理2个连通块,但是,作者选择用2个深度优先搜索分别找到连通块1和连通块2所包含的点的坐标。当然这需要储存,作者用了vector动态数组(vector<Da> vec[2]
,vec[0]表示第一个连通块,vec[1]表示第二个连通块),它的基本类型是结构体Da,Da的两个成员变量x,y是坐标。
找到所有的块过后,我们可以用2重for循环找到连通块1和连通块2中的块所有的搭配,并用ans储存它们之间距离的最小值。这里并不需要用广度优先搜索找到最短距离,而是用了一个求曼哈顿距离的公式。即(i,j)到(m,n)的曼哈顿距离=|i-m|+|j-n|。C++里绝对值的函数是fabs(建议不要用abs),在cmath库里。但是这个公式求的是两个点的曼哈顿距离,不会出现重叠面积的情况,而现在是两个块——在运用时会重叠一块面积,于是要将所得结果减1。
啊哈?你们觉得哪种方法简单呢?
题外话
这次考试我们和高中的新生一起考,十分的兴奋。说考递归和二分查找,作者抓紧复习了一中午的二分查找,结果没有一道题考这个知识点。不过也好,作者没有发挥失常,3道题全部AC,终于体验了一把AK的爽。不久就是NOI大赛了,有没有一些同学和我一样,也在期待11月呢?
程序样例
真的不会超时吗?同学们的做法——深度优先搜索+广度优先搜索:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m,pre[1000000],a[1000000],b[1000000],minn,ans=1e10;
int x[4]={1,-1,0,0},y[4]={0,0,1,-1};
char map[100][100];
bool mark[100][100];
bool check(int s,int t)
{
if(s&&t&&s<=n&&t<=m&&!mark[s][t]&&map[s][t]!='S') return 1;
return 0;
}
void fun(int d)
{
minn++;
if(pre[d]) fun(pre[d]);
}
void bfs(int r,int c)
{
memset(mark,0,sizeof(mark));
minn=0;
int head=0,tail=1;
int nextr,nextc;
mark[r][c]=1;
pre[1]=0;
a[1]=r;
b[1]=c;
while(head!=tail)
{
head++;
for(int i=0;i<4;i++)
{
nextr=a[head]+x[i];
nextc=b[head]+y[i];
if(check(nextr,nextc))
{
tail++;
a[tail]=nextr;
b[tail]=nextc;
mark[nextr][nextc]=1;
pre[tail]=head;
if(map[nextr][nextc]=='X')
{
fun(tail);
ans=min(minn,ans);
}
}
}
}
}
void dfs(int r,int c)
{
for(int i=0;i<4;i++)
if(check(r+x[i],c+y[i])&&map[r+x[i]][c+y[i]]!='.')
{
mark[r+x[i]][c+y[i]]=1;
map[r+x[i]][c+y[i]]='S';
dfs(r+x[i],c+y[i]);
mark[r+x[i]][c+y[i]]=0;
}
}
void f()
{
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(map[i][j]=='X')
{
dfs(i,j);
map[i][j]='S';
return ;
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%s",map[i]+1);
bool flag=1;
f();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(map[i][j]=='S')
bfs(i,j);
printf("%d",ans-2);
}
作者觉得还是自己的好点。深度优先搜索+曼哈顿距离:
/*Lucky_Glass*/
#include<cstdio>
#include<vector>
#include<cmath>
#include<algorithm>
using namespace std;
struct Da{int x,y;} D;
vector<Da> vec[2];
char map[55][55];
int F[4][2]={{1,0},{-1,0},{0,1},{0,-1}},r,c,S;
void flag(int x,int y)
{
D.x=x;D.y=y;
vec[S].push_back(D);
map[x][y]='.';
for(int i=0;i<4;i++)
{
int sx=x+F[i][0],sy=y+F[i][1];
if(0<=sx && sx<r && 0<=y && y<c && map[sx][sy]=='X')
flag(sx,sy);
}
}
int main()
{
scanf("%d%d",&r,&c);
for(int i=0;i<r;i++)
scanf("%s",map[i]);
for(int i=0;i<r;i++)
for(int j=0;j<c;j++)
if(map[i][j]=='X')
{
flag(i,j);
S++;
}
int la=vec[0].size(),lb=vec[1].size(),ans=1e8;
for(int i=0;i<la;i++)
for(int j=0;j<lb;j++)
ans=min(ans,int(fabs(vec[0][i].x-vec[1][j].x)+fabs(vec[0][i].y-vec[1][j].y)-1));
printf("%d\n",ans);
return 0;
}