끝없이 넓은 모눈 판에 전선을 놓는다.
칸은 정수 좌표 (x, y)로 가리키고, 처음에는 모든 칸이 비어 있다.
첫 줄에 명령 수 q가, 다음 q줄에 명령이 한 줄에 하나씩 주어진다.
"lay x y 길"은 칸 (x, y)에서 시작해서 길의 글자를 앞에서부터 따라 한 칸씩 움직이며 전선 하나를 놓는 명령이다.
글자 U는 y가 1 커지는 칸으로, D는 y가 1 작아지는 칸으로, L은 x가 1 작아지는 칸으로, R은 x가 1 커지는 칸으로 움직인다.
전선은 시작 칸과 움직여서 닿은 모든 칸을 차지한다.
전선이 자기가 이미 지난 칸을 다시 지나도 되고, 그런 칸은 한 번만 센다.
지나는 칸 가운데 다른 전선이 차지한 칸이 하나라도 있으면 이 전선은 놓지 못한다.
이때는 "blocked 번호"를 출력하고, 번호는 시작 칸부터 길 순서로 보았을 때 처음 만나는 차지된 칸의 전선 번호이다.
놓지 못한 전선은 판에 한 칸도 남기지 않는다.
막히지 않았으면 전선이 놓이고, 그 전선이 차지한 칸 수를 "ok 칸수"로 출력한다.
놓인 전선은 놓인 순서대로 1, 2, 3, … 번호를 받고, 놓지 못한 전선은 번호를 받지 않는다.
"cut x y"는 칸 (x, y)를 차지한 전선을 통째로 걷어 내는 명령이다.
그런 전선이 있으면 그 전선이 차지한 모든 칸이 비고 "removed 번호"를 출력하며, 없으면 "none"을 출력한다.
걷어 낸 전선의 번호는 다시 쓰지 않는다.
모든 명령을 처리한 뒤 마지막 줄에 판에 남은 전선 수와 차지된 칸 수를 빈칸 하나로 띄워 출력하라.
입력
1 ≤ q ≤ 2,000. 명령의 x, y는 −1,000,000,000 이상 1,000,000,000 이하의 정수이고 음수는 앞에 -가 붙는다. 길은 U, D, L, R로 된 1 ~ 100글자. 어느 때든 차지된 칸은 9,000개 이하
출력
명령마다 한 줄(ok 칸수, blocked 번호, removed 번호, none), 마지막 줄에 남은 전선 수와 차지된 칸 수
ok 4
blocked 1
ok 4
removed 1
none
ok 3
none
ok 2
ok 2
4 11
예시 2 · stdin
5
lay 1000000000 1000000000 L
lay 1000000000 999999999 L
lay 999999997 999999999 RRUL
cut 999999999 999999999
cut -1000000000 -1000000000
stdout
ok 2
ok 2
blocked 2
removed 2
none
1 2
main.py
오류 목록00F8 ↓ · ⇧F8 ↑
입력(stdin)
실행하면 이 줄들이 차례로 input()·표준 입력으로 들어가요(실행 중에 묻지 않아요).
$ 준비됐어요
코드를 입력해 풀려면 PC에서 열어 주세요. 모바일에서는 문제와 풀이를 볼 수 있어요.
다른 언어로는?언어별 풀이 보기 · 8개 언어 풀이
'이 칸을 누가 차지했나'를 칸 → 전선 번호 사전에 열쇠가 있는지로 묻는 문제예요. 좌표가 ±10억이라 2차원 배열은 만들 수 없어요. 열쇠가 좌표 두 개라는 점에서 언어가 갈려요. Python은 튜플 (x, y)를 그대로 열쇠로 쓰고 (x, y) in owner로 물어요. C#은 값 튜플 Dictionary<(int, int), int>에 ContainsKey·TryGetValue, C++은 std::map<std::pair<int, int>, int>에 find를 써요(std::unordered_map은 pair의 해시가 없어서 그대로는 컴파일되지 않아요). JS·TS의 Map은 배열도 열쇠로 받지만 내용이 아니라 같은 배열인지만 봐요. Java는 long 하나로 묶은 HashMap<Long, Integer>와 containsKey·get, 크레이콘은 사전()에 "" + x + "," + y 문자열을 열쇠로 쓰고 owner.있나(key)로 물어요. C는 사전이 없어서 해시 표를 직접 만들고, 지운 자리에는 표시를 남겨 그 뒤의 칸을 계속 찾을 수 있게 해요. 함정 하나: JS map.has([1, 2])는 방금 넣은 [1, 2]와 다른 배열이라 늘 false예요. 첫 예시의 둘째 명령이 막히지 않아요. x + "," + y 같은 문자열을 열쇠로 써요. 함정 둘: 문자열로 만들 때 사이에 구분 글자가 없으면 (1, 23)과 (12, 3)이 둘 다 "123"이에요(첫 예시의 마지막 두 명령). 함정 셋: 수 하나로 묶을 때 JS x * 4000000000 + y는 4 × 10^18까지 가서 2^53을 넘고, 이웃한 칸이 같은 수가 돼요(둘째 예시의 둘째 명령). Java ((long) x << 32) | y는 y가 음수이면 위쪽 32비트가 모두 1이 되어 x가 사라져서 (0, −1)과 (1, −1)이 같은 열쇠예요(첫 예시의 cut 0 -1). y & 0xFFFFFFFFL로 아래 32비트만 남겨요. 함정 넷: 막힌 전선은 한 칸도 놓이지 않아요. 한 칸씩 놓으면서 검사하면 막히기 전에 놓은 칸이 남아서, 첫 예시의 여섯째 명령이 그 칸에 막혀요. 길 전체를 먼저 검사하고 나서 놓아요. 함정 다섯: 자기 길을 다시 지나는 칸은 막힌 것이 아니고 한 번만 세요. RULDR은 6칸이 아니라 4칸이에요. 함정 여섯: 자를 때는 열쇠를 지워요. Python del owner[cell], JS·TS delete(key), Java remove, C# Remove, C++ erase, 크레이콘 빼기예요. 값만 0으로 바꾸면 '열쇠가 있나'는 여전히 참이라 빈 칸이 차지된 것처럼 보여요. 함정 일곱: blocked의 번호는 길 순서로 처음 만난 칸의 전선이지, 막은 전선 가운데 번호가 가장 작은 것이 아니에요(둘째 예시의 셋째 명령).
Pythonmain.py · 44 줄
import sysSTEP = {"U": (0, 1), "D": (0, -1), "L": (-1, 0), "R": (1, 0)}def main(): lines = sys.stdin.read().split("\n") q = int(lines[0]) owner = {} # (x, y) → 전선 번호. 튜플은 값이 같으면 같은 열쇠다 wires = {} # 전선 번호 → 그 전선이 차지한 칸들 next_id = 1 out = [] for line in lines[1:1 + q]: parts = line.split() x, y = int(parts[1]), int(parts[2]) if parts[0] == "lay": path = [(x, y)] for ch in parts[3]: dx, dy = STEP[ch] x, y = x + dx, y + dy path.append((x, y)) hit = next((cell for cell in path if cell in owner), None) # 길 순서로 처음 막히는 칸 if hit is not None: out.append(f"blocked {owner[hit]}") # 아무 칸도 놓지 않는다 continue mine = list(dict.fromkeys(path)) # 자기 길을 다시 지나는 칸은 한 번만 for cell in mine: owner[cell] = next_id wires[next_id] = mine out.append(f"ok {len(mine)}") next_id += 1 else: if (x, y) in owner: wire = owner[(x, y)] for cell in wires.pop(wire): del owner[cell] # 값을 0으로 바꾸는 것이 아니라 열쇠를 지운다 out.append(f"removed {wire}") else: out.append("none") out.append(f"{len(wires)} {len(owner)}") print("\n".join(out))main()
JavaScriptmain.js · 43 줄
const lines = require("fs").readFileSync(0, "utf8").split("\n");const STEP = { U: [0, 1], D: [0, -1], L: [-1, 0], R: [1, 0] };const q = Number(lines[0]);const owner = new Map(); // "x,y" → 전선 번호. 배열 [x, y]는 내용이 같아도 다른 열쇠라서 문자열로 만든다const wires = new Map(); // 전선 번호 → 그 전선이 차지한 칸의 열쇠들let nextId = 1;const out = [];for (let t = 1; t <= q; t++) { const parts = lines[t].trim().split(" "); let x = Number(parts[1]); let y = Number(parts[2]); if (parts[0] === "lay") { const path = [x + "," + y]; for (const ch of parts[3]) { x += STEP[ch][0]; y += STEP[ch][1]; path.push(x + "," + y); } const hit = path.find((key) => owner.has(key)); // 길 순서로 처음 막히는 칸 if (hit !== undefined) { out.push("blocked " + owner.get(hit)); // 아무 칸도 놓지 않는다 continue; } const mine = [...new Set(path)]; // 자기 길을 다시 지나는 칸은 한 번만 for (const key of mine) owner.set(key, nextId); wires.set(nextId, mine); out.push("ok " + mine.length); nextId++; } else { const key = x + "," + y; if (owner.has(key)) { const wire = owner.get(key); for (const k of wires.get(wire)) owner.delete(k); // 값을 바꾸는 것이 아니라 열쇠를 지운다 wires.delete(wire); out.push("removed " + wire); } else { out.push("none"); } }}out.push(wires.size + " " + owner.size);console.log(out.join("\n"));
TypeScriptmain.ts · 45 줄
import { readFileSync } from "node:fs";const lines: string[] = readFileSync(0, "utf8").split("\n");const STEP: Record<string, [number, number]> = { U: [0, 1], D: [0, -1], L: [-1, 0], R: [1, 0] };const q: number = Number(lines[0]);const owner = new Map<string, number>(); // "x,y" → 전선 번호. 배열 [x, y]는 내용이 같아도 다른 열쇠라서 문자열로 만든다const wires = new Map<number, string[]>(); // 전선 번호 → 그 전선이 차지한 칸의 열쇠들let nextId = 1;const out: string[] = [];for (let t = 1; t <= q; t++) { const parts: string[] = lines[t].trim().split(" "); let x = Number(parts[1]); let y = Number(parts[2]); if (parts[0] === "lay") { const path: string[] = [x + "," + y]; for (const ch of parts[3]) { x += STEP[ch][0]; y += STEP[ch][1]; path.push(x + "," + y); } const hit: string | undefined = path.find((key: string) => owner.has(key)); // 길 순서로 처음 막히는 칸 if (hit !== undefined) { out.push("blocked " + owner.get(hit)); // 아무 칸도 놓지 않는다 continue; } const mine: string[] = [...new Set(path)]; // 자기 길을 다시 지나는 칸은 한 번만 for (const key of mine) owner.set(key, nextId); wires.set(nextId, mine); out.push("ok " + mine.length); nextId++; } else { const key: string = x + "," + y; const wire: number | undefined = owner.get(key); if (wire !== undefined) { for (const k of wires.get(wire) ?? []) owner.delete(k); // 값을 바꾸는 것이 아니라 열쇠를 지운다 wires.delete(wire); out.push("removed " + wire); } else { out.push("none"); } }}out.push(wires.size + " " + owner.size);console.log(out.join("\n"));
JavaMain.java · 63 줄
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.ArrayList;import java.util.HashMap;import java.util.LinkedHashSet;import java.util.List;import java.util.Map;import java.util.Set;public class Main { // 두 좌표를 long 하나로 묶는다. y가 음수이면 위쪽 32비트가 모두 1이 되므로 아래 32비트만 남기고 합친다. static long key(int x, int y) { return ((long) x << 32) | (y & 0xFFFFFFFFL); } public static void main(String[] args) throws IOException { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); int q = Integer.parseInt(in.readLine().trim()); Map<Long, Integer> owner = new HashMap<>(); // 칸 → 전선 번호 Map<Integer, List<Long>> wires = new HashMap<>(); // 전선 번호 → 그 전선이 차지한 칸들 int nextId = 1; StringBuilder out = new StringBuilder(); for (int t = 0; t < q; t++) { String[] parts = in.readLine().trim().split(" "); int x = Integer.parseInt(parts[1]), y = Integer.parseInt(parts[2]); if (parts[0].equals("lay")) { Set<Long> mine = new LinkedHashSet<>(); // 자기 길을 다시 지나는 칸은 한 번만 mine.add(key(x, y)); for (char ch : parts[3].toCharArray()) { if (ch == 'U') y++; else if (ch == 'D') y--; else if (ch == 'L') x--; else x++; mine.add(key(x, y)); } Integer hit = null; for (long k : mine) { // LinkedHashSet은 넣은 순서 = 길 순서 hit = owner.get(k); if (hit != null) break; } if (hit != null) { out.append("blocked ").append(hit).append('\n'); // 아무 칸도 놓지 않는다 continue; } for (long k : mine) owner.put(k, nextId); wires.put(nextId, new ArrayList<>(mine)); out.append("ok ").append(mine.size()).append('\n'); nextId++; } else { Integer wire = owner.get(key(x, y)); if (wire != null) { for (long k : wires.remove(wire)) owner.remove(k); // 열쇠를 지운다 out.append("removed ").append(wire).append('\n'); } else { out.append("none\n"); } } } out.append(wires.size()).append(' ').append(owner.size()).append('\n'); System.out.print(out); }}
C#Main.cs · 66 줄
using System;using System.Collections.Generic;using System.Text;class Program{ static void Main() { int q = int.Parse(Console.ReadLine().Trim()); var owner = new Dictionary<(int, int), int>(); // 칸 → 전선 번호. 값 튜플은 값이 같으면 같은 열쇠다 var wires = new Dictionary<int, List<(int, int)>>(); // 전선 번호 → 그 전선이 차지한 칸들 int nextId = 1; var sb = new StringBuilder(); for (int t = 0; t < q; t++) { string[] parts = Console.ReadLine().Trim().Split(' '); int x = int.Parse(parts[1]), y = int.Parse(parts[2]); if (parts[0] == "lay") { var path = new List<(int, int)> { (x, y) }; foreach (char ch in parts[3]) { if (ch == 'U') y++; else if (ch == 'D') y--; else if (ch == 'L') x--; else x++; path.Add((x, y)); } int hit = 0; foreach (var cell in path) { if (owner.TryGetValue(cell, out hit)) break; // 길 순서로 처음 막히는 칸 hit = 0; } if (hit != 0) { sb.Append("blocked ").Append(hit).Append('\n'); // 아무 칸도 놓지 않는다 continue; } var mine = new List<(int, int)>(); foreach (var cell in path) { if (owner.TryAdd(cell, nextId)) mine.Add(cell); // 자기 길을 다시 지나는 칸은 한 번만 } wires[nextId] = mine; sb.Append("ok ").Append(mine.Count).Append('\n'); nextId++; } else { if (owner.TryGetValue((x, y), out int wire)) { foreach (var cell in wires[wire]) owner.Remove(cell); // 열쇠를 지운다 wires.Remove(wire); sb.Append("removed ").Append(wire).Append('\n'); } else { sb.Append("none\n"); } } } sb.Append(wires.Count).Append(' ').Append(owner.Count).Append('\n'); Console.Write(sb); }}
C++main.cpp · 59 줄
#include <iostream>#include <map>#include <string>#include <utility>#include <vector>using namespace std;int main() { int q; cin >> q; map<pair<int, int>, int> owner; // 칸 → 전선 번호. pair는 값으로 비교된다 map<int, vector<pair<int, int>>> wires; // 전선 번호 → 그 전선이 차지한 칸들 int nextId = 1; for (int t = 0; t < q; t++) { string cmd; int x, y; cin >> cmd >> x >> y; if (cmd == "lay") { string steps; cin >> steps; vector<pair<int, int>> path = {{x, y}}; for (char ch : steps) { if (ch == 'U') y++; else if (ch == 'D') y--; else if (ch == 'L') x--; else x++; path.push_back({x, y}); } int hit = 0; for (const auto& cell : path) { auto it = owner.find(cell); // owner[cell]로 물으면 없는 칸이 0으로 생겨 버린다 if (it != owner.end()) { hit = it->second; break; } } if (hit != 0) { cout << "blocked " << hit << "\n"; // 아무 칸도 놓지 않는다 continue; } vector<pair<int, int>> mine; for (const auto& cell : path) { if (owner.insert({cell, nextId}).second) mine.push_back(cell); // 자기 길을 다시 지나는 칸은 한 번만 } cout << "ok " << mine.size() << "\n"; wires[nextId] = mine; nextId++; } else { auto it = owner.find({x, y}); if (it != owner.end()) { int wire = it->second; for (const auto& cell : wires[wire]) owner.erase(cell); // 열쇠를 지운다 wires.erase(wire); cout << "removed " << wire << "\n"; } else { cout << "none\n"; } } } cout << wires.size() << " " << owner.size() << "\n"; return 0;}
Cmain.c · 99 줄
#include <stdio.h>#include <string.h>/* C에는 사전이 없어서 해시 표를 직접 만든다. 칸은 (x, y, 전선 번호)이고, 빈 칸은 state 0, 쓰는 칸은 1, 지운 자리는 2(찾을 때 건너뛰고 계속 본다). */#define SLOTS (1 << 19) /* 놓는 칸은 모두 합쳐 2,000 × 101 = 202,000개 이하라 표가 차지 않는다 */#define MAXQ 2000#define MAXLEN 100static int slot_x[SLOTS], slot_y[SLOTS], slot_id[SLOTS];static char state[SLOTS];static int wire_x[MAXQ + 1][MAXLEN + 1], wire_y[MAXQ + 1][MAXLEN + 1], wire_cells[MAXQ + 1];static unsigned hash(int x, int y) { unsigned h = (unsigned)x * 2654435761u ^ ((unsigned)y * 40503u + 0x9e3779b9u); h ^= h >> 15; return h & (SLOTS - 1);}/* (x, y)가 든 자리를 돌려준다. 없으면 -1 */static int find(int x, int y) { unsigned h = hash(x, y); while (state[h] != 0) { if (state[h] == 1 && slot_x[h] == x && slot_y[h] == y) return (int)h; h = (h + 1) & (SLOTS - 1); } return -1;}static void insert(int x, int y, int id) { unsigned h = hash(x, y); while (state[h] == 1) h = (h + 1) & (SLOTS - 1); state[h] = 1; slot_x[h] = x; slot_y[h] = y; slot_id[h] = id;}int main(void) { int q; if (scanf("%d", &q) != 1) return 0; int next_id = 1, wire_count = 0, cell_count = 0; for (int t = 0; t < q; t++) { char cmd[8]; int x, y; if (scanf("%7s %d %d", cmd, &x, &y) != 3) return 0; if (strcmp(cmd, "lay") == 0) { char steps[MAXLEN + 2]; if (scanf("%101s", steps) != 1) return 0; int len = (int)strlen(steps); int px[MAXLEN + 1], py[MAXLEN + 1]; px[0] = x; py[0] = y; for (int i = 0; i < len; i++) { if (steps[i] == 'U') y++; else if (steps[i] == 'D') y--; else if (steps[i] == 'L') x--; else x++; px[i + 1] = x; py[i + 1] = y; } int hit = 0; for (int i = 0; i <= len && hit == 0; i++) { int at = find(px[i], py[i]); /* 길 순서로 처음 막히는 칸 */ if (at >= 0) hit = slot_id[at]; } if (hit != 0) { printf("blocked %d\n", hit); /* 아무 칸도 놓지 않는다 */ continue; } int n = 0; for (int i = 0; i <= len; i++) { if (find(px[i], py[i]) >= 0) continue; /* 자기 길을 다시 지나는 칸은 한 번만 */ insert(px[i], py[i], next_id); wire_x[next_id][n] = px[i]; wire_y[next_id][n] = py[i]; n++; } wire_cells[next_id] = n; wire_count++; cell_count += n; printf("ok %d\n", n); next_id++; } else { int at = find(x, y); if (at >= 0) { int wire = slot_id[at]; for (int i = 0; i < wire_cells[wire]; i++) state[find(wire_x[wire][i], wire_y[wire][i])] = 2; wire_count--; cell_count -= wire_cells[wire]; printf("removed %d\n", wire); } else { printf("none\n"); } } } printf("%d %d\n", wire_count, cell_count); return 0;}
Craykonmain.cray · 83 줄
q = 콘솔.줄읽기().수로변환();owner = 사전(); // "x,y" → 전선 번호. 열쇠는 두 좌표를 쉼표로 이은 문자열wires = 사전(); // 전선 번호 → 그 전선이 차지한 칸의 열쇠들nextId = 1;반복 (t = 0; t < q; t++){ parts = 콘솔.줄읽기().공백제거().나누기(" "); x = parts[1].수로변환(); y = parts[2].수로변환(); 만약 (parts[0] == "lay") { path = ["" + x + "," + y]; steps = parts[3]; 반복 (i = 0; i < steps.길이; i++) { ch = steps.부분문자열(i, 1); 만약 (ch == "U") { y = y + 1; } 아니면 만약 (ch == "D") { y = y - 1; } 아니면 만약 (ch == "L") { x = x - 1; } 아니면 { x = x + 1; } path.넣기("" + x + "," + y); } hit = 0; 각각 (key 에서 path) { 만약 (owner.있나(key)) { hit = owner[key]; // 길 순서로 처음 막히는 칸 그만; } } 만약 (hit != 0) { 콘솔.쓰기("blocked " + hit); // 아무 칸도 놓지 않는다 } 아니면 { mine = []; 각각 (key 에서 path) { 만약 (!owner.있나(key)) { owner[key] = nextId; mine.넣기(key); } } wires[nextId] = mine; 콘솔.쓰기("ok " + mine.개수); nextId = nextId + 1; } } 아니면 { key = "" + x + "," + y; 만약 (owner.있나(key)) { wire = owner[key]; 각각 (k 에서 wires[wire]) { owner.빼기(k); // 값을 바꾸는 것이 아니라 열쇠를 지운다 } wires.빼기(wire); 콘솔.쓰기("removed " + wire); } 아니면 { 콘솔.쓰기("none"); } }}콘솔.쓰기(wires.개수 + " " + owner.개수);