题目描述
伊朗伊斯兰革命卫队(某恐怖组织)正在策划一起刺杀行动,他们的目标是沙特驻美大 使朱拜尔。他们来到了沙特驻美使馆,准备完成此次刺杀,要进入使馆首先必须通过使馆前 的防御迷阵。
迷阵由 n*m 个相同的小房间组成,每个房间与相邻四个房间之间有门可通行。在第 n 行的 m 个房间里有 m 个机关,这些机关必须全部打开才可以进入大使馆。而第 1 行的 m 个 房间有 m 扇向外打开的门,是迷阵的入口。除了第 1 行和第 n 行的房间外,每个房间都被 使馆的安保人员安装了激光杀伤装置,将会对进入房间的人造成一定的伤害。第 i 行第 j 列 造成的伤害值为 p[i][j](第 1 行和第 n 行的 p 值全部为 0)。
现在伊斯兰革命卫队打算以最小伤害代价进入迷阵,打开全部机关,显然,他们可以选 择任意多的人从任意的门进入,但必须到达第 n 行的每个房间。一个士兵受到的伤害值为他 到达某个机关的路径上所有房间的伤害值中的最大值,整个部队受到的伤害值为所有士兵的 伤害值中的最大值。现在,这个恐怖组织掌握了迷阵的情况,他们需要提前知道怎么安排士 兵的行进路线可以使得整个部队的伤害值最小。
输入输出格式
输入格式:
第一行有两个整数 n,m,表示迷阵的大小。
接下来 n 行,每行 m 个数,第 i 行第 j 列的数表示 p[i][j]。
输出格式:
输出一个数,表示最小伤害代价。
输入输出样例
输入样例#1:
4 2
0 0
3 5
2 4
0 0
输出样例#1
3
说明
50%的数据,n,m<=100;
100%的数据,n,m<=1000,p[i][j]<=1000。
思路
并查集是个好东西。找出整个矩阵中的最大值,然后二分,符合条件的相邻的房间用并查集并到一个集合中。
#include <stdio.h>
#include <string.h>
#include <iostream>
using namespace std;
int n,m,a[1001][1001],s,maxn(-1);
int f[1000001];
int Getfind(int v)//并查集有“擒贼先擒王”的原则
{
if(f[v]==v)//直到找到祖宗位置
{
return v;
}
else//不停的去认爸爸
{//祖宗其实就是树的祖先(应该是),寻找这个家庭的祖宗编号。这里路径压缩了一下,提高速度
f[v]=Getfind(f[v]);
return f[v];
}
}
inline void merge(int x,int y)//合并两个子集合
{
int f1=Getfind(x);//f1、f2分别为x和y的boss
int f2=Getfind(y);
if(f1!=f2)//如果两个节点在同一集合,也就是是否是同一个祖先
{
f[f2]=f1;//这里是“靠左原则”,即把右边的集合作为左边集合的子集合
//其实f[f1]=f2也行。
}
}
int check(int cnt)
{
register int i,j;
for(i=1;i<=n*m;i++)//并查集的初始化,很重要,数组里存的是自己的下标编号
{
f[i]=i;
}
for(i=1;i<=n;i++)
{
for(j=1;j<=m;j++)
{
if(a[i][j]<=cnt)//当用小于等于当前值的格可以把1和n*m联通时,当前值合法
{
int now=(i-1)*m+j;
if(i+1<=n && a[i+1][j]<=cnt)
{
merge(now,i*m+j);
}
if(j+1<=m && a[i][j+1]<=cnt)
{
merge(now,now+1);
}
}
}
}
if(Getfind(1)==Getfind(n*m))
{
return 1;
}
return 0;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int i,j;
cin>>n>>m;
for(i=1;i<=n;i++)
{
for(j=1;j<=m;j++)
{
cin>>a[i][j];
maxn=max(a[i][j],maxn);//找出整个a数组中的最大值,作为二分的右边界
}
}
int mid,l(0),r(maxn);
while(l<=r)//题目说“最大值最小”,那就二分答案
{
mid=(l+r)>>1;
if(check(mid))//符合条件的相邻的格用并查集并在一起
{
r=mid-1;
s=mid;
}
else
{
l=mid+1;
}
}
cout<<s<<endl;
return 0;
}