汽车拉力比赛
汽车拉力比赛
https://www.luogu.com.cn/problem/P2658
这道题给大家介绍三种做法(并查集也能做,但本蒟不会懒)
First
算法:未知(不信你往下读)
同机房大佬在考试的时候想出来的神奇做法!!!
思路:对于每一个路标,求与之相连的四个格子与它的高度差中的最小值(边缘的不算)用tot来表示。然后从ans与tot中取较大值,也就是在所有路标对应的tot中取最大值。
什么?这是什么思路?能得分?答案是:这种思路实现出来,90分!(向所有绞尽脑计做出来还没有90分的同志表示致敬QAQ,我最开始深搜+二分才可怜的10分啊)
PS:据那位同学说,他是在看样例的时候发现的这个“规律”,想着反正打不出正解,能得多少算多少.......
code1:
#include <bits/stdc++.h>
using namespace std;
int m,n,a[501][501],lb[501][501],tot,ans;
int main() {
scanf("%d%d",&m,&n);
for(register int i=1;i<=m;i++) {
for(register int j=1;j<=n;j++) {
scanf("%d",&a[i][j]);
}
}
for(register int i=1;i<=m;i++) {
for(register int j=1;j<=n;j++) {
scanf("%d",&lb[i][j]);
}
}
for(register int i=1;i<=m;i++) {
for(register int j=1;j<=n;j++) {
if(lb[i][j]==1) {
tot=0x3f3f3f3f;
if(a[i][j-1]!=0) tot=min(tot,abs(a[i][j-1]-a[i][j]));
if(a[i-1][j]!=0) tot=min(tot,abs(a[i-1][j]-a[i][j]));
if(a[i+1][j]!=0) tot=min(tot,abs(a[i+1][j]-a[i][j]));
if(a[i][j+1]!=0) tot=min(tot,abs(a[i][j+1]-a[i][j]));
}
ans=max(ans,tot);
}
}
printf("%d",ans);
return 0;
}
Second
算法:深搜DFS+二分
思路:
1、用lb数组专门存储路标的坐标,用u累计路标个数
2、二分(注意:一定是从0开始枚举!)每次枚举的就是mid值,然后从第一个路标开始深搜
3、深搜中,遇到一个点就用p数组标记为true
4、深搜完,判断所有路标是否都被标记为true。如果有false,那么当前mid就不符合题意,修改左端点=mid+1。否则,修改右端点=mid-1,并将当前mid值保存在ans中。最后输出ans即可
嗯,是中规中矩的思路了!个人认为,程序易懂qvq
code2:
#include <bits/stdc++.h>
using namespace std;
int n,m,ans,u;
long long l=1,r,a[505][505];
int dx[4]={0,0,-1,1};
int dy[4]={-1,1,0,0};
bool p[505][505];
struct node {
int x,y;
}lb[250025];
inline void dfs(int x,int y,int kk) {
if(p[x][y]) return ;
p[x][y]=true; //将当前点标记为访问过
for(register int i=0;i<4;i++) { //上下左右四个方向
int xx=x+dx[i];
int yy=y+dy[i];
if(xx<1||xx>m||yy<1||yy>n||abs(a[xx][yy]-a[x][y])>kk) continue; //没有越界
dfs(xx,yy,kk);
}
return ;
}
int main() {
scanf("%d%d",&m,&n);
for(register int i=1;i<=m;i++) {
for(register int j=1;j<=n;j++) {
scanf("%lld",&a[i][j]);
r=max(r,a[i][j]); //最高海拔即为枚举的右端点,很显然任意两点海拔值都不可能比最高海拔值大
}
}
for(register int i=1;i<=m;i++) {
for(register int j=1;j<=n;j++) {
int s;
scanf("%d",&s);
if(s==1) {
lb[++u].x=i; //u累计路标总数;lb数组记录路标坐标
lb[u].y=j;
}
}
}
while(l<=r) { //二分枚举
int mid=(l+r)>>1;
bool pk=true;
memset(p,false,sizeof(p)); //记住每次枚举都要清零
dfs(lb[1].x,lb[1].y,mid); //从1号路标开始
for(register int i=1;i<=u;i++) {
if(p[lb[i].x][lb[i].y]==false) { //如果有路标没有被访问,即表示当前mid不符合题意,直接跳出
pk=false;
break;
}
}
if(pk==false) l=mid+1; //修改两个端点
else r=mid-1,ans=mid;
}
printf("%d",ans);
return 0;
}
Third
算法:广搜BFS+二分
思路:和深搜的基本一致,除了多用了st、en标记第一个路标的坐标,剩下的就是套广搜板子了。直接看代码吧qvq
#include <bits/stdc++.h>
using namespace std;
bool lb[505][505],pd=1,p[505][505];
int n,m,a[505][505],l,r,mid,ans,st,en,tp;
int dx[4]={0,1,0,-1};
int dy[4]={1,0,-1,0};
inline bool bfs() { //直接套广搜板子
queue <int> x, y;
int sum=1;
x.push(st);
y.push(en);
p[st][en]=1;
while(!x.empty ()) {
int xx=x.front(),yy=y.front();
if(sum==tp) return 1; //所有路标都被包含了就可以返回true了
x.pop();
y.pop();
for(register int i=0;i<4;i++) {
int xxx=xx+dx[i];
int yyy=yy+dy[i];
if(xxx<1||xxx>n||yyy<1||yyy>m||p[xxx][yyy]||abs(a[xxx][yyy]-a[xx][yy])>mid) continue;
if(lb[xxx][yyy]==1) sum++; //统计覆盖到的路标
x.push(xxx); //入队
y.push(yyy);
p[xxx][yyy]=1;
}
}
return 0;
}
int main () {
scanf("%d%d",&n,&m);
for(register int i=1;i<=n;i++) {
for(register int j=1;j<=m;j++) {
scanf("%d",&a[i][j]);
r=max(r,a[i][j]);
}
}
for(register int i=1;i<=n;i++) {
for(register int j=1;j<=m;j++) {
scanf("%d",&lb[i][j]);
if(lb[i][j]==1) tp++;
if(pd==1&&lb[i][j]==1) { //标记第一个路标
st=i;
en=j;
pd=0;
}
}
}
while(l<=r) {
mid=(l+r)>>1;
memset(p,0,sizeof p);
if(bfs()==true) {
r=mid-1;
ans=mid;
}
else l=mid+1;
}
printf("%d",ans);
return 0;
}
总结一下:
1、深搜和广搜的板子是肯定要背住的,遇到就直接套
2、二分要注意枚举的边界,这样可以节省很多时间耗费
PS:也许会有很多人说第一种做法90分是因为数据水,这确实是一个因素。但是在考试或者比赛时,谁也不知道数据是什么样的,要是能用这样简单的程序得高分,何乐不为?所以,运气也是实力的一部分(我就没发现这种做法,再次膜拜同机房大佬Orz)
哈哈,扯远了。那那那那就希望这篇题解对您有些许的帮助吧QVQ~~