{ }codelper
集合シミュレーションP0352

踏むと裏返るタイル

果てしなく広い床にタイルが敷かれていて、はじめはすべてのタイルが白である。
タイルの位置は 2 つの整数 (x, y) で表す。
右へ行くほど x が大きくなり、上へ行くほど y が大きくなる。
ロボットはタイル (0, 0) から出発する。
出発するときに立っているタイルは裏返らない。
命令 n 個が順に与えられる。
命令は方向と歩数 k からなり、ロボットはその方向へ 1 歩で 1 タイルずつ k 歩進もうとする。
方向は U(上)、D(下)、L(左)、R(右)のいずれかである。
ロボットが 1 歩進んで踏んだタイルが白なら、そのタイルは黒に変わり、ロボットは残りの歩数を続ける。
踏んだタイルが黒なら、そのタイルは白に変わり、ロボットはそのタイルに立ったまま、その命令の残りの歩数を捨てる。
次の命令はその位置から始まる。
すべての命令が終わったあとの黒いタイルの数と、ロボットが立っている位置を出力せよ。

入力
1 行目に n が与えられる。
続く n 行に命令が 1 行に 1 つずつ、方向の文字と k が空白 1 つで区切られて与えられる。
1 ≤ n ≤ 2,000、1 ≤ k ≤ 9,000 で、すべての命令の k の合計は 9,000 以下である
出力
1 行目に黒いタイルの数、2 行目にロボットが立っている位置の x と y を空白 1 つで区切って出力
例 1 · stdin
3
R 3
L 5
R 2
stdout
1
3 0
例 2 · stdin
6
R 11
U 10
L 10
D 12
L 3
R 5
stdout
41
-1 0
main.py
入力(stdin)

実行すると、これらの行が順に input()・標準入力に渡されます(実行中に入力を求めません)。

$ 準備完了

コードを入力して解くにはパソコンで開いてください。モバイルでは問題と解答を見られます。

ほかの言語では?言語別の解答を見る · 8 言語の解答

黒いタイルの位置を集合に入れ、踏むたびに入っているかを調べます(Python in、JS has、Java contains、C# Contains、C++ find、Craykon 含む)。
難しいのは、数 2 つの位置を 1 つの要素として入れる方法が言語ごとに違うことです。
Python はタプル (x, y) を、C# はタプルを HashSet<(int, int)> に、C++ は pair を std::set にそのまま入れれば、値で比べてくれます(C++ の unordered_set は pair のハッシュがないので、そのままではコンパイルできません)。
Java は record を作ると equals と hashCode が値にもとづいて作られます。
JS・TS と Craykon は文字列 "x,y" に変えて入れます。
Craykon は 辞書() のキーを集合のように使い、取り除く で消します。
C には集合がないので、位置を配列にためて先頭から探します。
歩数が 9,000 しかないので、それでも十分速いです。
落とし穴 1:JS・TS の Set に配列 [x, y] を入れると、中身が同じでも別の要素として扱われ、has はいつも false です。
例 1 では黒いタイルを一度も見分けられず、10 と 0 0 が出ます。
Java の HashSet<int[]> と C# の HashSet<int[]> も同じく false を返します。
落とし穴 2:Python で集合にリスト [x, y] を入れると TypeError(unhashable type: 'list')で止まります。
変更できるリストではなくタプルを使います。
落とし穴 3:文字列のキーを区切り文字なしでつなぐと、違う位置が同じキーになります。
例 2 では (11, 0) と (1, 10) がどちらも "110" になるので、白いタイル (1, 10) を黒いタイルと取り違えて止まり、41 ではなく 39 が出ます。

Pythonmain.py · 20 行
import sys MOVES = {"U": (0, 1), "D": (0, -1), "L": (-1, 0), "R": (1, 0)} lines = sys.stdin.read().split("\n")n = int(lines[0])black = set()          # 검은 타일의 자리 (x, y)x = y = 0for line in lines[1:n + 1]:    d, k = line.split()    dx, dy = MOVES[d]    for _ in range(int(k)):        x += dx        y += dy        if (x, y) in black:            black.remove((x, y))   # 검은 타일: 흰색으로 뒤집고 남은 걸음을 버린다            break        black.add((x, y))          # 흰 타일: 검은색으로 뒤집고 이어 간다print(len(black))print(x, y)
JavaScriptmain.js · 23 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");const n = Number(lines[0]);const MOVES = { U: [0, 1], D: [0, -1], L: [-1, 0], R: [1, 0] };// 배열 [x, y]는 내용이 같아도 다른 원소로 치므로, 문자열 "x,y"를 넣는다const black = new Set();let x = 0;let y = 0;for (let i = 1; i <= n; i++) {  const [d, k] = lines[i].trim().split(" ");  const [dx, dy] = MOVES[d];  for (let step = 0; step < Number(k); step++) {    x += dx;    y += dy;    const key = `${x},${y}`;    if (black.has(key)) {      black.delete(key); // 검은 타일: 흰색으로 뒤집고 남은 걸음을 버린다      break;    }    black.add(key); // 흰 타일: 검은색으로 뒤집고 이어 간다  }}console.log(black.size);console.log(`${x} ${y}`);
TypeScriptmain.ts · 25 行
import { readFileSync } from "node:fs"; const lines: string[] = readFileSync(0, "utf8").split("\n");const n: number = Number(lines[0]);const MOVES: Record<string, [number, number]> = { U: [0, 1], D: [0, -1], L: [-1, 0], R: [1, 0] };// 배열 [x, y]는 내용이 같아도 다른 원소로 치므로, 문자열 "x,y"를 넣는다const black = new Set<string>();let x = 0;let y = 0;for (let i = 1; i <= n; i++) {  const [d, k]: string[] = lines[i].trim().split(" ");  const [dx, dy]: [number, number] = MOVES[d];  for (let step = 0; step < Number(k); step++) {    x += dx;    y += dy;    const key: string = `${x},${y}`;    if (black.has(key)) {      black.delete(key); // 검은 타일: 흰색으로 뒤집고 남은 걸음을 버린다      break;    }    black.add(key); // 흰 타일: 검은색으로 뒤집고 이어 간다  }}console.log(black.size);console.log(`${x} ${y}`);
JavaMain.java · 40 行
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.HashSet;import java.util.Set; public class Main {    // record는 equals와 hashCode를 값으로 만들어 주므로 집합에 그대로 넣을 수 있다    record Tile(int x, int y) {}     public static void main(String[] args) throws IOException {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        int n = Integer.parseInt(in.readLine().trim());        Set<Tile> black = new HashSet<>();        int x = 0, y = 0;        for (int i = 0; i < n; i++) {            String[] parts = in.readLine().trim().split(" ");            int dx = 0, dy = 0;            switch (parts[0]) {                case "U" -> dy = 1;                case "D" -> dy = -1;                case "L" -> dx = -1;                default -> dx = 1;            }            int k = Integer.parseInt(parts[1]);            for (int step = 0; step < k; step++) {                x += dx;                y += dy;                Tile tile = new Tile(x, y);                if (black.contains(tile)) {                    black.remove(tile); // 검은 타일: 흰색으로 뒤집고 남은 걸음을 버린다                    break;                }                black.add(tile); // 흰 타일: 검은색으로 뒤집고 이어 간다            }        }        System.out.println(black.size());        System.out.println(x + " " + y);    }}
C#Main.cs · 39 行
using System;using System.Collections.Generic; class Program{    static void Main()    {        int n = int.Parse(Console.ReadLine().Trim());        // 튜플 (x, y)는 값이 같으면 같은 원소다        var black = new HashSet<(int, int)>();        int x = 0, y = 0;        for (int i = 0; i < n; i++)        {            string[] parts = Console.ReadLine().Trim().Split(' ');            int dx = 0, dy = 0;            switch (parts[0])            {                case "U": dy = 1; break;                case "D": dy = -1; break;                case "L": dx = -1; break;                default: dx = 1; break;            }            int k = int.Parse(parts[1]);            for (int step = 0; step < k; step++)            {                x += dx;                y += dy;                if (black.Contains((x, y)))                {                    black.Remove((x, y)); // 검은 타일: 흰색으로 뒤집고 남은 걸음을 버린다                    break;                }                black.Add((x, y)); // 흰 타일: 검은색으로 뒤집고 이어 간다            }        }        Console.WriteLine(black.Count);        Console.WriteLine($"{x} {y}");    }}
C++main.cpp · 36 行
#include <iostream>#include <set>#include <utility>using namespace std; int main() {    ios::sync_with_stdio(false);    cin.tie(nullptr);    int n;    cin >> n;    // pair는 값으로 견주므로 std::set에 그대로 넣을 수 있다    set<pair<int, int>> black;    int x = 0, y = 0;    for (int i = 0; i < n; i++) {        char d;        int k;        cin >> d >> k;        int dx = 0, dy = 0;        if (d == 'U') dy = 1;        else if (d == 'D') dy = -1;        else if (d == 'L') dx = -1;        else dx = 1;        for (int step = 0; step < k; step++) {            x += dx;            y += dy;            auto it = black.find({x, y});            if (it != black.end()) {                black.erase(it); // 검은 타일: 흰색으로 뒤집고 남은 걸음을 버린다                break;            }            black.insert({x, y}); // 흰 타일: 검은색으로 뒤집고 이어 간다        }    }    cout << black.size() << '\n' << x << ' ' << y << '\n';    return 0;}
Cmain.c · 49 行
#include <stdio.h> #define MAXSTEPS 9000 /* C에는 집합이 없다. 검은 타일의 자리를 배열에 모아 두고 처음부터 찾는다.   걸음이 많아야 9,000번이라 검은 타일도 9,000개를 넘지 않는다. */static int bx[MAXSTEPS], by[MAXSTEPS];static int count = 0; /* (x, y)가 든 칸 번호를 돌려준다. 없으면 -1 */static int find(int x, int y) {    for (int i = 0; i < count; i++) {        if (bx[i] == x && by[i] == y) return i;    }    return -1;} int main(void) {    int n;    scanf("%d", &n);    int x = 0, y = 0;    for (int i = 0; i < n; i++) {        char d;        int k;        scanf(" %c %d", &d, &k);        int dx = 0, dy = 0;        if (d == 'U') dy = 1;        else if (d == 'D') dy = -1;        else if (d == 'L') dx = -1;        else dx = 1;        for (int step = 0; step < k; step++) {            x += dx;            y += dy;            int at = find(x, y);            if (at >= 0) {                /* 검은 타일: 마지막 칸을 그 자리로 옮겨 지우고, 남은 걸음을 버린다 */                count--;                bx[at] = bx[count];                by[at] = by[count];                break;            }            bx[count] = x; /* 흰 타일: 검은색으로 뒤집고 이어 간다 */            by[count] = y;            count++;        }    }    printf("%d\n%d %d\n", count, x, y);    return 0;}
Craykonmain.cray · 43 行
n = コンソール.行読み().数に();// 사전의 열쇠를 집합처럼 쓴다. 자리는 문자열 "x,y"로 바꿔 넣는다black = 辞書();x = 0;y = 0;繰り返す (i = 0; i < n; i++){    parts = コンソール.行読み().空白除去().分ける();    dx = 0;    dy = 0;    選択 (parts[0])    {        場合 "U":            dy = 1;            抜ける;        場合 "D":            dy = -1;            抜ける;        場合 "L":            dx = -1;            抜ける;        それ以外:            dx = 1;            抜ける;    }    k = parts[1].数に();    繰り返す (step = 0; step < k; step++)    {        x += dx;        y += dy;        key = x + "," + y;        もし (black.含む(key))        {            // 검은 타일: 흰색으로 뒤집고 남은 걸음을 버린다            black.取り除く(key);            抜ける;        }        // 흰 타일: 검은색으로 뒤집고 이어 간다        black[key] = 真;    }}コンソール.書く(black.個数);コンソール.書く(x + " " + y);