《算法竞赛·快冲300题 》将于2024年出版,是《算法竞赛》的辅助练习册。
所有题目放在自建的OJ New Online Judge。
用C/C++、Java、Python三种语言给出代码,以中低档题为主,适合入门、进阶。
文章目录
" 游泳 " ,链接: http://oj.ecustacm.cn/problem.php?id=1753
题目描述
【题目描述】 游泳池可以等分为n行n列的小区域,每个区域的温度不同。
小明现在在要从游泳池的左上角(1, 1)游到右下角(n, n),小明只能向上下左右四个方向游,不能游出泳池。
而小明对温度十分敏感,他希望你帮他找一条最舒适的路径,使路径上的最高的水温和最低的水温差值最小。
【输入格式】 第一行输入一个正整数n。接下来n行,每行n个正整数,表示方阵每个区域的温度a[i][j]。所有数据保证随机。(1≤n≤100,1≤a[i][j]≤1000)。
【输出格式】
一行一个数表示最小差值。
【输入样例】
c
4
1 3 10 8
1 4 10 8
1 1 1 1
1 5 8 8
【输出样例】
c
7
题解
在图上找一条路径,如果是最短路径,用BFS最好;如果用DFS,会导致搜索大量路径,必须剪枝才能减少搜索。如果不是最短路径,用DFS更好编码更简单,注意也需要剪枝。
本题要求找一条温差最小的路径,如果简单地遍历出所有路径,再比较得到温差最小路径,肯定超时,因为总路径数量是 O ( 4 n ) O(4^n) O(4n)的。必须剪枝才能减少路径的搜索数量。
如何剪枝?如果已知最小温差,只要一边游一边检查当前路径上的最大温差,如果已经超过了允许的最小温差,就不用走下去了。但是最小温差不能预知,只能猜,最好的方法是使用二分法来猜这个最小温差。本题的解法是"DFS + 二分法"。 用"BFS+二分法"也行,请读者思考。
【重点】 DFS+二分法。
C++代码
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 110;
int n;
int a[N][N]; //温度
bool vis[N][N]; //是否已经访问过
int dx[4] = {1,-1,0,0}, dy[4] = {0,0,1,-1}; //上下左右四个方向
void dfs(int x,int y,int t_max,int t_min){
if(a[x][y] > t_max || a[x][y] < t_min) return; //剪枝
vis[x][y] = true;
for(int i = 0; i < 4; i++){
int nx = x + dx[i];
int ny = y + dy[i];
if(!vis[nx][ny] && nx >= 1 && nx <= n && ny >= 1 && ny <= n)
dfs(nx,ny,t_max,t_min);
}
}
bool check(int x){
for(int i = 1; i + x <= 1000; i++){
memset(vis,0,sizeof vis);
dfs(1,1,i + x,i);
if(vis[n][n]) return 1;
}
return 0;
}
int main(){
scanf("%d",&n);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
scanf("%d",&a[i][j]);
int left = 0,right = 1000;
while(left+1 < right){
int mid = (left + right)/2;
if(check(mid)) right = mid;
else left = mid;
}
printf("%d",right);
return 0;
}
Java代码
java
import java.util.*;
public class Main {
static final int N = 110;
static int n;
static int[][] a = new int[N][N]; // 温度
static boolean[][] vis = new boolean[N][N]; // 是否已经访问过
static int[] dx = {1, -1, 0, 0}, dy = {0, 0, 1, -1}; // 上下左右四个方向
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
n = scan.nextInt();
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
a[i][j] = scan.nextInt();
int left = 0, right = 1000;
while (left + 1 < right) {
int mid = (left + right) / 2;
if (check(mid)) right = mid;
else left = mid;
}
System.out.println(right);
scan.close();
}
static boolean check(int x) {
for (int i = 1; i + x <= 1000; i++) {
memset(vis, false);
dfs(1, 1, i + x, i);
if (vis[n][n]) return true;
}
return false;
}
static void dfs(int x, int y, int t_max, int t_min) {
if (a[x][y] > t_max || a[x][y] < t_min) return; // 剪枝
vis[x][y] = true;
for (int i = 0; i < 4; i++) {
int nx = x + dx[i];
int ny = y + dy[i];
if (!vis[nx][ny] && nx >= 1 && nx <= n && ny >= 1 && ny <= n)
dfs(nx, ny, t_max, t_min);
}
}
static void memset(boolean[][] arr, boolean val) {
for (int i = 0; i < arr.length; i++)
Arrays.fill(arr[i], val);
}
}
Python代码
python
import sys
sys.setrecursionlimit(1000000)
N = 110
n = int(input())
a = [[0]*N for _ in range(N)] # 温度
vis = [[False]*(N) for _ in range(N)] # 是否已经访问过
dx, dy = [1, -1, 0, 0], [0, 0, 1, -1] # 上下左右四个方向
def dfs(x, y, t_max, t_min):
if a[x][y] > t_max or a[x][y] < t_min: return # 剪枝
vis[x][y] = True
for i in range(4):
nx = x + dx[i]
ny = y + dy[i]
if (not vis[nx][ny]) and 1 <= nx <= n and 1 <= ny <= n:
dfs(nx, ny, t_max, t_min)
def check(x):
for i in range(1, 1000 - x + 1):
global vis
vis = [[False]*N for _ in range(N)]
dfs(1, 1, i + x, i)
if vis[n][n]: return True
return False
for i in range(1,n+1):
a[i] = [0] + list(map(int, input().split()))
print(a[n][n])
left, right = 0, 1000
while left + 1 < right:
mid = (left + right) // 2
if check(mid): right = mid
else: left = mid
print(right)