{ }codelper
集合模拟P0352

踩上去就翻面的地砖

无边无际的地面上铺着地砖,一开始所有地砖都是白色。
地砖的位置用两个整数 (x, y) 表示。
越往右 x 越大,越往上 y 越大。
机器人从地砖 (0, 0) 出发。
出发时站着的地砖不翻面。
依次给出 n 条命令。
一条命令由方向和步数 k 组成,机器人打算朝这个方向走 k 步,每步走一块地砖。
方向是 U(上)、D(下)、L(左)、R(右)之一。
机器人走一步踩到的地砖如果是白色,这块地砖变成黑色,机器人继续走剩下的步数。
踩到的地砖如果是黑色,这块地砖变成白色,机器人停在这块地砖上,放弃这条命令剩下的步数。
下一条命令从这里开始。
输出所有命令结束后黑色地砖的数量和机器人所在的位置。

输入
第一行给出 n。
接下来 n 行,每行一条命令:方向字母和 k,用一个空格隔开。
1 ≤ n ≤ 2,000,1 ≤ k ≤ 9,000,所有命令的 k 之和不超过 9,000
输出
第一行输出黑色地砖的数量,第二行输出机器人所在位置的 x 和 y,用一个空格隔开
样例 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 包含)。
难点是:怎样把由两个数组成的位置当作一个元素放进去,各语言做法不同。
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 步,这样也足够快。
陷阱一:往 JS、TS 的 Set 里放数组 [x, y],内容相同也算不同的元素,has 永远是 false。
在示例 1 中一次也认不出黑色地砖,会输出 10 和 0 0。
Java 的 HashSet<int[]> 和 C# 的 HashSet<int[]> 同样给出 false。
陷阱二:在 Python 里往集合放列表 [x, y],会以 TypeError(unhashable type: 'list')停下。
要用元组,而不是可以修改的列表。
陷阱三:拼字符串键时如果中间不加分隔符,不同的位置会得到相同的键。
在示例 2 中 (11, 0) 和 (1, 10) 都是 "110",于是把白色地砖 (1, 10) 错当成黑色而停下,结果是 39 而不是 41。

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);