「アルゴリズム コンペティション: クイック 300 問」は 2024 年に出版される予定で、 「アルゴリズム コンペティション」の補助問題集です。
すべての質問は、自作の OJ New Online Judgeに配置されます。
コードは C/C++、Java、Python で提供されており、トピックは主に低レベルから中レベルで、初心者から上級者まで適しています。
「 スーパーナイト 」、リンク: http://oj.ecustacm.cn/problem.php?id=1810
トピックの説明
【タイトル説明】今、無限の次元で、あなたにスーパーナイトを与えます。
スーパーナイトには N 通りの歩き方がありますが、このスーパーナイトは平面上のすべての地点に到達できるかどうか聞いてもいいですか。
それぞれの歩き方に 2 つの数字 xx と yy を入力します。これは、スーパー ナイトが任意の点 (x, y) から (x+xx, y+yy) まで歩くことができることを示します。
[入力形式] 1行目にはテストデータがT個あることを示す正の整数Tを入力します。(1≤T≤100)
テスト データの各セットについて、最初の行に正の整数 N を入力します。これは、N 通りの移動方法があることを示します。(1≤N≤100)
次の N 行には、各行に 2 つの正の整数 xx と yy が含まれます。(-100≤xx,yy≤100)。
【出力形式】テストデータごとに、平面上の全点に到達できればYes、到達できなければNoを出力します。。
【入力サンプル】
2
3
1 0
0 1
-2 -1
5
3 4
-3 -6
2 -2
5 6
-1 4
【出力例】
Yes
No
答え
すべての点に到達できるかどうかを問う問題ですが、すべての点に到達できるかどうかを実際に確認する必要はありません。平面上の特定の点を確認する限り、そこからその上下左右の点に到達できれば、任意の点まで延長すれば、その上下左右に到達でき、平面全体に到達できます。
タイトルは-100≦xx、yy≦100としていますが、中心点(x,y)が(100,100)だとすると、一番遠くまで行けるのは(0,0),(0,200),(200,0),(200,200)となり、この4点で決まる区間内のすべての点を調べます。最後に、(x, y) の上下左右の点に到達できるかどうかを確認します。
この質問は単純な走査問題です。BFS または DFS を使用できます。計算の複雑さは、区間内のポイントの数です。合計 200*200 = 40000 ポイントです。BFS と DFS を使用して 1 回の走査が可能です。ただし、DFS にはスタック領域に制限があるため、この質問の DFS は大規模なスタックを使用する必要があります。保険を考えるとBFSを使うのが良いでしょう。
【重要】 DFSが使用するスタックサイズに注意してください。
C++ コード
以下は DFS コードです。シンプルですが、小さなトリックもあります。中心点 (X, Y) を起点として、その間の点を巡回していきます。いつでも (X, Y) の上下左右に到達したことがわかれば、他の点を巡回することなく、すぐに「Yes」を返すことができます。これは枝刈りの応用であり、コードの 8 行目でこの判断を行っています。DFS コードを作成するときは、それが枝刈りできるかどうかに注意を払う必要があります。
#include<bits/stdc++.h>
using namespace std;
int n, xx[110], yy[110];
int X=100, Y=100; //中心点(X,Y),从它出发
bool vis[210][210];
bool dfs(int x, int y){
//从(x,y)出发,把可以到达的点全部打上标记
vis[x][y] = true; //把当前点标记为已达
if(vis[X-1][Y] && vis[X+1][Y] && vis[X][Y-1] && vis[X][Y+1]) return true; //有剪枝的作用
for(int i = 1; i <= n; i++) {
//遍历n个方向
int nx = x + xx[i]; //新坐标(nx, ny)
int ny = y + yy[i];
if(nx < 1 || nx > 200 || ny < 1 || ny > 200) continue; //判断越界
if(vis[nx][ny]) continue; //已经走过,不用再走
if(dfs(nx, ny)) return true;
}
return false;
}
int main(){
int T; cin >> T;
while(T--) {
cin >> n;
for(int i = 1; i <= n; i++) cin >> xx[i] >> yy[i];
memset(vis, 0, sizeof(vis));
if(dfs(X, Y)) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
return 0;
}
BFS コード:
#include<bits/stdc++.h>
using namespace std;
int n,xx[110], yy[110];
int X=100,Y=100; //中心点(X,Y),从它出发
bool vis[210][210];
bool bfs(int x,int y){
//从(x,y)出发,把可以到达的点全部打上标记
vis[x][y] = true; //把当前点标记为已达
queue<pair<int,int>>q;
q.push({
x,y});
while(q.size()) {
auto t = q.front();
q.pop();
if(vis[X-1][Y] && vis[X+1][Y] && vis[X][Y-1] && vis[X][Y+1]) return true;
for(int i=1;i<=n;i++) {
//遍历n个方向
int nx = t.first+xx[i], ny = t.second+yy[i];
if(nx < 1 || nx > 200 || ny < 1 || ny > 200) continue; //判断越界
if(vis[nx][ny]) continue; //已经走过,不用再走
vis[nx][ny] = true;
q.push({
nx,ny});
}
}
return false;
}
int main(){
int T; cin>>T;
while(T--){
cin>>n;
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++) cin>>xx[i]>>yy[i];
if(bfs(X,Y)) cout<<"Yes\n";
else cout<<"No\n";
}
return 0;
}
Javaコード
DFS コードですが、エラーが発生し、スタックがオーバーフローしたため、スタックを拡張する必要があります。
import java.util.Scanner;
public class Main {
static int n;
static int[] xx = new int[110];
static int[] yy = new int[110];
static int X = 100, Y = 100; // 中心点(X,Y),从它出发
static boolean[][] vis = new boolean[210][210];
public static boolean dfs(int x, int y) {
vis[x][y] = true; // 把当前点标记为已达
if (vis[X - 1][Y] && vis[X + 1][Y] && vis[X][Y - 1] && vis[X][Y + 1])
return true; // 有剪枝的作用
for (int i = 1; i <= n; i++) {
// 遍历n个方向
int nx = x + xx[i]; // 新坐标(nx, ny)
int ny = y + yy[i];
if (nx < 1 || nx > 200 || ny < 1 || ny > 200) continue; // 判断越界
if (vis[nx][ny]) continue; // 已经走过,不用再走
if (dfs(nx, ny)) return true;
}
return false;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int T = scanner.nextInt();
while (T-- > 0) {
n = scanner.nextInt();
for (int i = 1; i <= n; i++) {
xx[i] = scanner.nextInt();
yy[i] = scanner.nextInt();
}
for (int i = 0; i < 210; i++)
for (int j = 0; j < 210; j++)
vis[i][j] = false;
if (dfs(X, Y)) System.out.println("Yes");
else System.out.println("No");
}
}
}
BFS コード: DFS のスタック領域について心配する必要はありません。したがって、保険を考えるとBFSを使用する方が良いでしょう。
import java.util.*;
public class Main {
static int n, X = 100, Y = 100;
static int[] xx = new int[110], yy = new int[110];
static boolean[][] vis = new boolean[210][210];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int T = sc.nextInt();
while (T-- > 0) {
n = sc.nextInt();
for (int i = 0; i < 210; i++)
for (int j = 0; j < 210; j++)
vis[i][j] = false;
for (int i = 1; i <= n; i++) {
xx[i] = sc.nextInt();
yy[i] = sc.nextInt();
}
if (bfs(X, Y)) System.out.println("Yes");
else System.out.println("No");
}
sc.close();
}
static boolean bfs(int x, int y) {
vis[x][y] = true;
Queue<int[]> q = new LinkedList<>();
q.offer(new int[]{
x, y});
while (!q.isEmpty()) {
int[] t = q.poll();
if (vis[X - 1][Y] && vis[X + 1][Y] && vis[X][Y - 1] && vis[X][Y + 1]) return true;
for (int i = 1; i <= n; i++) {
int nx = t[0] + xx[i], ny = t[1] + yy[i];
if (nx < 1 || nx > 200 || ny < 1 || ny > 200) continue;
if (vis[nx][ny]) continue;
vis[nx][ny] = true;
q.offer(new int[]{
nx, ny});
}
}
return false;
}
}
Pythonコード
DFS コードの場合は、setrecursionlimit を使用してスタックを拡張することに注意してください。
import sys
sys.setrecursionlimit(1000000)
n = 0
xx,yy = [0] * 110, [0] * 110
X,Y = 100,100 # 中心点(X,Y),从它出发
vis = [[False] * 210 for _ in range(210)]
def dfs(x, y): # 从(x,y)出发,把可以到达的点全部打上标记
global vis
vis[x][y] = True # 把当前点标记为已达
if vis[X-1][Y] and vis[X+1][Y] and vis[X][Y-1] and vis[X][Y+1]: return True #有剪枝的作用
for i in range(1, n + 1): # 遍历n个方向
nx = x + xx[i] # 新坐标(nx, ny)
ny = y + yy[i]
if nx < 1 or nx > 200 or ny < 1 or ny > 200: continue # 判断越界
if vis[nx][ny]: continue # 已经走过,不用再走
if dfs(nx, ny): return True
return False
T = int(input())
while T > 0:
T -= 1
n = int(input())
for i in range(1, n + 1): xx[i], yy[i] = map(int, input().split())
vis = [[False] * 210 for _ in range(210)]
if dfs(X, Y): print("Yes")
else: print("No")
BFS コード:
from queue import Queue
n, X, Y = 0, 100, 100
xx, yy = [0]*110, [0]*110
vis = [[False]*210 for _ in range(210)]
def bfs(x, y):
vis[x][y] = True
q = Queue()
q.put((x, y))
while not q.empty():
t = q.get()
if vis[X-1][Y] and vis[X+1][Y] and vis[X][Y-1] and vis[X][Y+1]: return True
for i in range(1, n+1):
nx, ny = t[0]+xx[i], t[1]+yy[i]
if nx < 1 or nx > 200 or ny < 1 or ny > 200 or vis[nx][ny]: continue
vis[nx][ny] = True
q.put((nx, ny))
return False
T = int(input())
while T:
T -= 1
n = int(input())
for i in range(1, n + 1): xx[i], yy[i] = map(int, input().split())
vis = [[False]*210 for _ in range(210)]
if bfs(X, Y): print('Yes')
else: print('No')