AtCoder

ABC311 D問題(Grid Ice Floor)を解く

AtCoder_ABC311_D

AtCoder が提供しているABC(AtCoder Beginner Contest)311 のD問題をC++とPythonで解いてみました。ABC311は、2023年7月22日21:00に実施されました。

AtCoder の紹介はこちらに、プログラミングの方針はこちらに記事があります。

D問題 Grid Ice Floor(Difficulty : 885)

問題はリンク先をご覧ください。

ABC311 D問題 Grid Ice Floor

グリッドを処理する問題です。DFSを使いました。AtCoder Problems による Difficulty は 885 でした。

解答案

問題から、左上の座標から問題に与えられている行動(氷の上をすべる)で、たどり着けるマスの数を調べます。与えられるグリッドは岩で囲まれているため、解きやすくなっています。

以下の方針で、グリッドの氷(’.’)を触れることができる点(’o’)に書き換えていきます。

  • 与えられた点から、岩があるまで与えれた方向に進む。進みながらグリッドの文字を ‘o’ に書き換える。
  • 岩で止まった点から、以下の条件を満たす場合に上下左右に向かって進める。
    • その方向に進むと書き換えていない ‘.’ がある。

書き換えていない氷があることを確認しない場合、入力例2の右上にある ‘C’ の文字の中に入ると(実際に入ります)、C の中をぐるぐると調べ続けます。このため、書き換える氷がないことを確認する必要があります。

また、一度とまった点は二度と調べないという方法も考えられます。

###########
.###...####
.#..#...###
.#...#...##
.#........#
.#...#....#
.#..#.....#
.###......#
..........#

C++ プログラム例(ABC311D)

今回は、 私が慣れている DFS(深さ優先探索:Depth-First-Search)を使いました。上の方針に従い実装します。プログラムでの留意点は以下です。

  • 右下左上の移動を配列に格納しておく(6、7行目)。
  • 再帰関数 dfs の処理
    • 与えられた点を ‘o’ に書き換える(11行目)。
    • 与えられた方向に岩があるまで書き換えながら進む(12ー16行目)。
    • その地点から、4方向について、まだ氷が残っているか調べる(19ー29行目)。
    • もし、氷が残っていれば、dfs を再帰的に呼び出す(30-32行目)。
  • dfs(1, 1, 右) と dfs(1, 1, 下) を呼び出す。
  • グリッドの ‘o’ の個数をカウントして出力する。

以下が、C++プログラムとなります。

#include <bits/stdc++.h>
using namespace std;

vector<string> s;

int diff_x[4] = {1,  0, -1,  0};
int diff_y[4] = {0,  1,  0, -1};

void dfs(int x, int y, int d)
{
	s[y][x] = 'o';
	while (s[y + diff_y[d]][x + diff_x[d]] != '#') {
		s[y + diff_y[d]][x + diff_x[d]] = 'o';
		x += diff_x[d];
		y += diff_y[d];
	}

	for (int i = 0; i < 4; ++i) {
		bool this_dir = false;
		int this_x = x;
		int this_y = y;
		while (s[this_y + diff_y[i]][this_x + diff_x[i]] != '#') {
			if (s[this_y + diff_y[i]][this_x + diff_x[i]] == '.') {
				this_dir = true;
				break;
			}
			this_x += diff_x[i];
			this_y += diff_y[i];
		}
		if (this_dir) {
			dfs(x + diff_x[i], y + diff_y[i], i);
		}
	}
}

int main()
{
	int n, m;
	cin >> n >> m;
	s.resize(n);
	for (int i = 0; i < n; ++i) {
		cin >> s[i];
	}

	dfs(1, 1, 0);
	dfs(1, 1, 1);

	int result = 0;
	for (int i = 0; i < n; ++i) {
		for (int j = 0; j < m; ++j) {
			if (s[i][j] == 'o') {
				++result;
			}
		}
	}

	cout << result << endl;

	return 0;
}

AC(Accepted=正しいプログラム)と判定されました。

Python プログラム例(ABC311D)

Python では、文字列を書き換えることができないため、リストのリスト t に変換しました(36ー42行目)。t の値は、0:氷、1:岩、2:触れることができる点 となります。

再帰関数の呼出回数を sys.setrecursionlimit で増やしています。また Python の慣用に従い、定数としか使わない変数名を大文字にしました(DIFF_X/DIFF_Y)。

"""AtCoder Beginner Contest 311 D"""
import sys

LIMIT = 10**6
sys.setrecursionlimit(LIMIT + 1)

DIFF_X = [1,  0, -1,  0]
DIFF_Y = [0,  1,  0, -1]


def dfs(x, y, d):
    global t

    t[y][x] = 2
    while t[y + DIFF_Y[d]][x + DIFF_X[d]] != 1:
        t[y + DIFF_Y[d]][x + DIFF_X[d]] = 2
        x += DIFF_X[d]
        y += DIFF_Y[d]

    for i in range(4):
        this_dir = False
        this_x = x
        this_y = y
        while t[this_y + DIFF_Y[i]][this_x + DIFF_X[i]] != 1:
            if t[this_y + DIFF_Y[i]][this_x + DIFF_X[i]] == 0:
                this_dir = True
                break
            this_x += DIFF_X[i]
            this_y += DIFF_Y[i]
        if this_dir:
            dfs(x + DIFF_X[i], y + DIFF_Y[i], i)


n, m = map(int, input().split())
s = [input() for i in range(n)]
t = [[0 for i in range(m)] for j in range(n)]
for i in range(n):
    for j in range(m):
        if s[i][j] == '#':
            t[i][j] = 1
        else:
            t[i][j] = 0

dfs(1, 1, 0)
dfs(1, 1, 1)

result = 0
for i in range(n):
    for j in range(m):
        if t[i][j] == 2:
            result += 1

print(result)

こちらも「AC」と判定されました。

最後に

この問題は楽しく解くことができました。ただし、途中でプログラムを間違えてデバッグに手間取り、AC が取れたのは、95分1秒でした。

D問題は解けるようになってきたので、早く解くことが必要になってきたのかもしれません。

引き続き ABC の問題を紹介していきます。

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA