게임 채팅방이 같은 말을 되풀이하는 도배를 거른다.
첫 줄에 화면에 남는 메시지 수 k와 차단 기준 s가 빈칸 하나로 띄워 주어진다.
둘째 줄에 메시지 수 n이 주어지고, 다음 n줄에 메시지가 "이름 낱말" 꼴로 한 줄에 하나씩 주어진다.
채팅방에는 화면 목록과 차단 목록이 있고, 둘 다 처음에는 비어 있다.
메시지를 받은 차례대로 하나씩, 아래 세 가지를 위에서부터 살펴 맨 먼저 맞는 것 하나만 한다.
1. 보낸 이름이 차단 목록에 있으면 그 메시지를 무시한다. 이때 다른 것은 아무것도 바뀌지 않는다.
2. 화면 목록에 이름이 같고 낱말도 같은 메시지가 있으면 도배다. 이 메시지는 출력하지 않고 화면 목록에도 넣지 않으며, 그 이름의 경고 횟수를 1 늘린다. 그 이름의 경고 횟수가 s가 되면 그 이름을 차단 목록의 맨 뒤에 넣는다.
3. 그 밖의 메시지는 화면에 보인다. 받은 그대로 "이름 낱말"을 한 줄에 출력하고, 화면 목록의 맨 뒤에 넣는다. 넣은 뒤 화면 목록의 메시지가 k개보다 많으면 가장 먼저 넣은 메시지 하나를 뺀다.
낱말이 같은지는 영어 대문자와 소문자를 구별하지 않고 본다. 그래서 hi와 HI는 같은 낱말이다.
이름은 글자가 모두 같아야 같은 이름이다.
이름이 다르면 낱말이 같아도 도배가 아니다.
경고 횟수는 이름마다 0에서 시작하고 줄어들지 않는다.
이름이 차단되어도 그 이름으로 화면 목록에 이미 들어 있는 메시지는 빼지 않는다.
모든 메시지를 처리한 뒤, 도배로 거른 메시지 수와 무시한 메시지 수를 빈칸 하나로 띄워 한 줄에 출력한다.
마지막 줄에는 차단 목록의 이름을 차단된 차례대로 빈칸 하나씩 띄워 출력한다. 차단된 이름이 없으면 "-"를 출력한다.
입력
1 ≤ k ≤ 50, 1 ≤ s ≤ 5, 1 ≤ n ≤ 2,000. 이름은 영어 소문자 1~8글자, 낱말은 영어 대문자·소문자 1~8글자이고, 이름과 낱말 사이는 빈칸 하나다
출력
화면에 보인 메시지마다 한 줄(받은 그대로), 이어서 "도배 수 무시 수" 한 줄, 마지막 줄에 차단된 이름들(없으면 -)
예시 1 · stdin
2 2
10
mina hi
jun hi
mina HI
jun yo
mina hi
mina Hi
mina hi
jun yo
ab c
a bc
stdout
mina hi
jun hi
jun yo
mina hi
ab c
a bc
3 1
mina
main.py
오류 목록00F8 ↓ · ⇧F8 ↑
입력(stdin)
실행하면 이 줄들이 차례로 input()·표준 입력으로 들어가요(실행 중에 묻지 않아요).
$ 준비됐어요
코드를 입력해 풀려면 PC에서 열어 주세요. 모바일에서는 문제와 풀이를 볼 수 있어요.
다른 언어로는?언어별 풀이 보기 · 8개 언어 풀이
이 문제는 "목록에 있나"를 두 번 물어요. 차단 목록에는 이름 하나를, 화면 목록에는 (이름, 낱말) 쌍을 물어요. 문자열 하나를 찾는 것은 Python name in blocked, JS·TS blocked.includes(name), Java blocked.contains(name), C# blocked.Contains(name), C++ std::find(blocked.begin(), blocked.end(), name) != blocked.end(), 크레이콘 blocked.있나(name)이고, C는 strcmp로 하나씩 견주는 반복문을 써요. 쌍을 찾을 때 언어가 갈려요. Python 튜플 (name, low) in screen, C++ std::pair를 넣은 std::find, C# 튜플을 담은 List<(string, string)>의 Contains, Java List.of(name, low)를 담은 목록의 contains는 속의 값을 견줘요. 함정 하나: JS·TS screen.includes([name, low]), Java List<String[]>의 contains(new String[] {name, low}), 크레이콘 screen.있나([name, low])는 속이 같아도 방금 만든 다른 배열·목록이라 늘 거짓이에요. 오류 없이 도배를 하나도 못 잡아서 예시의 셋째 메시지 mina HI가 그대로 출력돼요. JS·TS는 screen.some(([a, b]) => a === name && b === low)로 속을 견주고, 크레이콘은 이름 목록과 낱말 목록을 나란히 두고 자리마다 견줘요. 함정 둘: 쌍을 문자열 하나로 이어 붙여 찾을 수도 있지만, 사이 글자 없이 name + low로 이으면 ab + c와 a + bc가 똑같이 abc가 되어 예시의 마지막 메시지 a bc가 도배로 걸러져요. 이름과 낱말에 나올 수 없는 빈칸을 사이에 넣어야 해요. 함정 셋: 대소문자를 구별하지 않으려면 넣을 때도 찾을 때도 소문자로 바꾼 낱말을 써요(Python lower(), JS·TS toLowerCase(), Java toLowerCase(Locale.ROOT), C# ToLowerInvariant(), C·C++ 글자마다 tolower, 크레이콘 소문자로변환()). 바꾸지 않으면 mina HI가 도배가 아니게 돼요. 출력은 받은 그대로라서 원래 낱말도 들고 있어야 해요. 함정 넷: 이름을 빼고 낱말만 찾으면 예시의 둘째 메시지 jun hi처럼 다른 사람이 한 같은 말까지 도배가 돼요. 함정 다섯: 도배로 거른 메시지를 화면 목록에 넣으면 화면에 있던 메시지가 밀려나거나 남아서, 예시의 다섯째 메시지 mina hi가 도배로 잘못 걸러져요. 함정 여섯: 넣은 뒤 >= k일 때 빼면 화면 목록에 k − 1개만 남아요. 맨 앞을 빼는 것은 Python pop(0), JS·TS shift(), Java remove(0), C# RemoveAt(0), C++ erase(begin()), 크레이콘 자리빼기(0)이고, C는 배열을 한 칸씩 당겨요. 함정 일곱: 차단 목록을 먼저 물어야 해요. 화면 목록을 먼저 물으면 이미 차단된 mina의 일곱째 메시지가 무시가 아니라 도배로 세어져서 3 1이 4 0이 돼요.
Pythonmain.py · 28 줄
import syslines = sys.stdin.read().split("\n")k, s = map(int, lines[0].split())n = int(lines[1])screen = [] # 화면에 남은 (이름, 소문자 낱말) 쌍, 오래된 것이 앞blocked = [] # 차단된 이름, 차단된 차례대로warn = {}repeats = ignored = 0out = []for i in range(n): name, word = lines[2 + i].split() key = (name, word.lower()) if name in blocked: ignored += 1 elif key in screen: # 튜플은 값으로 비교한다 repeats += 1 warn[name] = warn.get(name, 0) + 1 if warn[name] == s: blocked.append(name) else: out.append(f"{name} {word}") screen.append(key) if len(screen) > k: screen.pop(0)out.append(f"{repeats} {ignored}")out.append(" ".join(blocked) if blocked else "-")print("\n".join(out))
JavaScriptmain.js · 28 줄
const lines = require("fs").readFileSync(0, "utf8").split("\n");const [k, s] = lines[0].trim().split(/\s+/).map(Number);const n = Number(lines[1]);const screen = []; // 화면에 남은 [이름, 소문자 낱말] 쌍, 오래된 것이 앞const blocked = []; // 차단된 이름, 차단된 차례대로const warn = new Map();let repeats = 0;let ignored = 0;const out = [];for (let i = 0; i < n; i++) { const [name, word] = lines[2 + i].trim().split(/\s+/); const low = word.toLowerCase(); if (blocked.includes(name)) { ignored++; } else if (screen.some(([a, b]) => a === name && b === low)) { // includes([name, low])는 새 배열이라 늘 false다. 속을 하나씩 견준다 repeats++; warn.set(name, (warn.get(name) ?? 0) + 1); if (warn.get(name) === s) blocked.push(name); } else { out.push(`${name} ${word}`); screen.push([name, low]); if (screen.length > k) screen.shift(); }}out.push(`${repeats} ${ignored}`);out.push(blocked.length > 0 ? blocked.join(" ") : "-");console.log(out.join("\n"));
TypeScriptmain.ts · 30 줄
import { readFileSync } from "node:fs";const lines: string[] = readFileSync(0, "utf8").split("\n");const [k, s]: number[] = lines[0].trim().split(/\s+/).map(Number);const n: number = Number(lines[1]);const screen: [string, string][] = []; // 화면에 남은 [이름, 소문자 낱말] 쌍, 오래된 것이 앞const blocked: string[] = []; // 차단된 이름, 차단된 차례대로const warn = new Map<string, number>();let repeats = 0;let ignored = 0;const out: string[] = [];for (let i = 0; i < n; i++) { const [name, word]: string[] = lines[2 + i].trim().split(/\s+/); const low: string = word.toLowerCase(); if (blocked.includes(name)) { ignored++; } else if (screen.some(([a, b]) => a === name && b === low)) { // includes([name, low])는 새 배열이라 늘 false다. 속을 하나씩 견준다 repeats++; const count: number = (warn.get(name) ?? 0) + 1; warn.set(name, count); if (count === s) blocked.push(name); } else { out.push(`${name} ${word}`); screen.push([name, low]); if (screen.length > k) screen.shift(); }}out.push(`${repeats} ${ignored}`);out.push(blocked.length > 0 ? blocked.join(" ") : "-");console.log(out.join("\n"));
JavaMain.java · 44 줄
import java.io.BufferedReader;import java.io.InputStreamReader;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Locale;import java.util.Map;public class Main { public static void main(String[] args) throws Exception { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); String[] first = in.readLine().trim().split(" "); int k = Integer.parseInt(first[0]); int s = Integer.parseInt(first[1]); int n = Integer.parseInt(in.readLine().trim()); // List.of(이름, 낱말)은 equals가 속을 견주므로 contains로 찾을 수 있다(String[]는 안 된다) List<List<String>> screen = new ArrayList<>(); List<String> blocked = new ArrayList<>(); Map<String, Integer> warn = new HashMap<>(); int repeats = 0; int ignored = 0; StringBuilder out = new StringBuilder(); for (int i = 0; i < n; i++) { String[] parts = in.readLine().trim().split(" "); String name = parts[0]; String word = parts[1]; List<String> key = List.of(name, word.toLowerCase(Locale.ROOT)); if (blocked.contains(name)) { ignored++; } else if (screen.contains(key)) { repeats++; int count = warn.merge(name, 1, Integer::sum); if (count == s) blocked.add(name); } else { out.append(name).append(' ').append(word).append('\n'); screen.add(key); if (screen.size() > k) screen.remove(0); } } out.append(repeats).append(' ').append(ignored).append('\n'); out.append(blocked.isEmpty() ? "-" : String.join(" ", blocked)).append('\n'); System.out.print(out); }}
C#Main.cs · 45 줄
using System;using System.Collections.Generic;using System.Text;class Program{ static void Main() { string[] first = Console.ReadLine().Trim().Split(' '); int k = int.Parse(first[0]); int s = int.Parse(first[1]); int n = int.Parse(Console.ReadLine()); // 튜플 (string, string)은 값으로 비교하므로 Contains로 찾을 수 있다(string[]은 안 된다) var screen = new List<(string, string)>(); var blocked = new List<string>(); var warn = new Dictionary<string, int>(); int repeats = 0, ignored = 0; var sb = new StringBuilder(); for (int i = 0; i < n; i++) { string[] parts = Console.ReadLine().Trim().Split(' '); string name = parts[0], word = parts[1]; var key = (name, word.ToLowerInvariant()); if (blocked.Contains(name)) { ignored++; } else if (screen.Contains(key)) { repeats++; warn[name] = warn.GetValueOrDefault(name, 0) + 1; if (warn[name] == s) blocked.Add(name); } else { sb.Append(name).Append(' ').Append(word).Append('\n'); screen.Add(key); if (screen.Count > k) screen.RemoveAt(0); } } sb.Append(repeats).Append(' ').Append(ignored).Append('\n'); sb.Append(blocked.Count > 0 ? string.Join(" ", blocked) : "-").Append('\n'); Console.Write(sb); }}
C++main.cpp · 42 줄
#include <algorithm>#include <cctype>#include <iostream>#include <map>#include <string>#include <utility>#include <vector>using namespace std;int main() { int k, s, n; cin >> k >> s >> n; vector<pair<string, string>> screen; // 화면에 남은 (이름, 소문자 낱말), 오래된 것이 앞 vector<string> blocked; // 차단된 이름, 차단된 차례대로 map<string, int> warn; int repeats = 0, ignored = 0; for (int i = 0; i < n; i++) { string name, word; cin >> name >> word; string low = word; for (char &c : low) c = (char)tolower((unsigned char)c); pair<string, string> key{name, low}; if (find(blocked.begin(), blocked.end(), name) != blocked.end()) { ignored++; } else if (find(screen.begin(), screen.end(), key) != screen.end()) { // pair는 값으로 비교한다 repeats++; if (++warn[name] == s) blocked.push_back(name); } else { cout << name << ' ' << word << '\n'; screen.push_back(key); if ((int)screen.size() > k) screen.erase(screen.begin()); } } cout << repeats << ' ' << ignored << '\n'; if (blocked.empty()) { cout << "-\n"; } else { for (size_t i = 0; i < blocked.size(); i++) cout << (i ? " " : "") << blocked[i]; cout << '\n'; } return 0;}
Cmain.c · 68 줄
#include <ctype.h>#include <stdio.h>#include <string.h>#define MAXK 50#define MAXN 2000static char scr_name[MAXK][9], scr_word[MAXK][9]; /* 화면 목록, 오래된 것이 앞 */static char people[MAXN][9]; /* 도배한 적이 있는 이름 */static int warn[MAXN];static char blocked[MAXN][9];/* C에는 목록 찾기가 없다. strcmp로 하나씩 견준다 */static int find_name(char list[][9], int count, const char *name) { for (int i = 0; i < count; i++) if (strcmp(list[i], name) == 0) return i; return -1;}int main(void) { int k, s, n; if (scanf("%d %d %d", &k, &s, &n) != 3) return 1; int screen = 0, npeople = 0, nblocked = 0, repeats = 0, ignored = 0; for (int i = 0; i < n; i++) { char name[9], word[9], low[9]; if (scanf("%8s %8s", name, word) != 2) return 1; size_t len = strlen(word); for (size_t j = 0; j <= len; j++) low[j] = (char)tolower((unsigned char)word[j]); if (find_name(blocked, nblocked, name) >= 0) { ignored++; continue; } int on_screen = 0; for (int j = 0; j < screen; j++) if (strcmp(scr_name[j], name) == 0 && strcmp(scr_word[j], low) == 0) on_screen = 1; if (on_screen) { repeats++; int p = find_name(people, npeople, name); if (p < 0) { p = npeople++; strcpy(people[p], name); warn[p] = 0; } if (++warn[p] == s) strcpy(blocked[nblocked++], name); } else { printf("%s %s\n", name, word); if (screen == k) { /* 꽉 찼으면 가장 오래된 것을 빼고 한 칸씩 당긴다 */ for (int j = 1; j < screen; j++) { strcpy(scr_name[j - 1], scr_name[j]); strcpy(scr_word[j - 1], scr_word[j]); } screen--; } strcpy(scr_name[screen], name); strcpy(scr_word[screen], low); screen++; } } printf("%d %d\n", repeats, ignored); if (nblocked == 0) { printf("-\n"); } else { for (int i = 0; i < nblocked; i++) printf("%s%s", i ? " " : "", blocked[i]); printf("\n"); } return 0;}
Craykonmain.cray · 66 줄
first = 콘솔.수들읽기();k = first[0];s = first[1];n = 콘솔.줄읽기().수로변환();names = []; // 화면에 남은 메시지의 이름, 오래된 것이 앞words = []; // 같은 자리의 소문자 낱말blocked = []; // 차단된 이름, 차단된 차례대로warn = 사전();repeats = 0;ignored = 0;반복 (i = 0; i < n; i++){ parts = 콘솔.줄읽기().나누기(); name = parts[0]; low = parts[1].소문자로변환(); 만약 (blocked.있나(name)) { ignored += 1; } 아니면 만약 (onScreen(names, words, name, low)) { repeats += 1; 만약 (!warn.있나(name)) { warn[name] = 0; } warn[name] += 1; 만약 (warn[name] == s) { blocked.넣기(name); } } 아니면 { 콘솔.쓰기(name + " " + parts[1]); names.넣기(name); words.넣기(low); 만약 (names.개수 > k) { names.자리빼기(0); words.자리빼기(0); } }}콘솔.쓰기(repeats + " " + ignored);만약 (blocked.개수 > 0){ 콘솔.쓰기(blocked.잇기(" "));}아니면{ 콘솔.쓰기("-");}// 목록.있나([name, low])는 새 목록이라 늘 거짓이다. 이름과 낱말을 자리마다 견준다onScreen(names, words, name, low){ 반복 (j = 0; j < names.개수; j++) { 만약 (names[j] == name && words[j] == low) { 돌려주기 참; } } 돌려주기 거짓;}