「アルゴリズム コンペティション: クイック 300 問」は 2024 年に出版される予定で、 「アルゴリズム コンペティション」の補助問題集です。
すべての質問は、自作の OJ New Online Judgeに配置されます。
コードは C/C++、Java、Python で提供されており、トピックは主に低レベルから中レベルで、初心者から上級者まで適しています。
「 水泳 」、リンク: http://oj.ecustacm.cn/problem.php?id=1753
トピックの説明
【タイトル説明】プールはn行n列の小さなエリアに分けることができ、エリアごとに温度が異なります。
シャオ ミンはこれからプールの左上隅 (1, 1) から右下隅 (n, n) まで泳ごうとしていますが、シャオ ミンは上下左右の 4 方向にしか泳ぐことができず、プールから出ることはできません。
そして、シャオミンは温度に非常に敏感なので、パス上の最高水温と最低水温の差が最小になるように、彼が最も快適なパスを見つけるのを手伝ってほしいと彼は望んでいます。
[入力形式] 1行目に正の整数nを入力します。次の n 行は、それぞれ n 個の正の整数を持ち、正方行列の各領域の温度 a[i][j] を表します。すべてのデータはランダムであることが保証されます。(1≤n≤100、1≤a[i][j]≤1000)。
[出力形式]
1 行に 1 つの数値が最小の差を表します。
【入力サンプル】
4
1 3 10 8
1 4 10 8
1 1 1 1
1 5 8 8
【出力例】
7
答え
グラフ上でパスを見つけます。それが最短のパスである場合は、BFS を使用するのが最適です。DFS を使用する場合は、多数のパスを検索することになるため、検索を減らすために枝刈りする必要があります。最短パスでない場合は、DFS を使用してより適切にエンコードする方が簡単ですが、プルーニングも必要になることに注意してください。
この質問では、温度差が最小のパスを見つける必要があります。単純にすべてのパスを走査し、温度差が最小のパスを比較すると、パスの総数は O ( 4 n ) O(4^n) であるため、確実にタイムアウトになります。○ (4n )。枝刈りは、検索するパスの数を減らすために必要です。
剪定方法は?最小温度差がわかっている場合は、泳ぎながら現在の経路の最大温度差を確認するだけでよく、最小温度差を超えている場合は進む必要はありません。ただし、最小温度差は予測できず、推測することしかできません。最良の方法は、二分法を使用して最小温度差を推測することです。この質問に対する解決策は、「DFS + 二分法」です。「BFS + 二分法」を使用することも可能です。読者は考えてください。
【ポイント】DFS+の二分法。
C++ コード
#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コード
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コード
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)