{ }codelper
列表包含删除元素大小写P0391

按屏幕上还留着的话过滤刷屏

游戏聊天室要过滤重复说同一句话的刷屏。
第一行给出屏幕上保留的消息数 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
输入(stdin)

运行时这些行会依次作为 input()/标准输入传入(运行中不会等待输入)。

$ 就绪

请在电脑上打开以编写和运行代码。在手机上可以查看题目和题解。

其他语言怎么写?查看各语言题解 · 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(),Craykon 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})、Craykon screen.包含([name, low]) 永远为假,因为内容相同也是刚创建的另一个数组或列表。
不会报错,却一条刷屏也抓不到,示例的第 3 条消息 mina HI 会照样输出。
JS/TS 用 screen.some(([a, b]) => a === name && b === low) 比较内容,Craykon 则并排保存名字列表和单词列表,逐个位置比较。
陷阱二:也可以把这一对连接成一个字符串再查找,但不加分隔符地写成 name + low,ab + c 和 a + bc 都会变成 abc,示例的最后一条消息 a bc 会被当作刷屏过滤掉。
要在中间放一个名字和单词里都不会出现的空格。
陷阱三:要不区分大小写,放入和查找时都使用转成小写的单词(Python lower(),JS/TS toLowerCase(),Java toLowerCase(Locale.ROOT),C# ToLowerInvariant(),C/C++ 逐个字符 tolower,Craykon 小写())。
不转换的话 mina HI 就不算刷屏。
输出要按收到的原样,所以还要保留原来的单词。
陷阱四:不看名字只查找单词,会像示例的第 2 条消息 jun hi 那样把别人说的同一句话也当成刷屏。
陷阱五:把被过滤的刷屏消息也放入屏幕列表,屏幕上原有的消息会被挤掉或留得太久,示例的第 5 条消息 mina hi 会被错误地过滤。
陷阱六:放入后在 >= k 时移除,屏幕列表里只剩 k − 1 条。
移除最前面的元素是 Python pop(0),JS/TS shift(),Java remove(0),C# RemoveAt(0),C++ erase(begin()),Craykon 按位置移除(0),C 则把数组逐个向前挪。
陷阱七:要先问封禁列表。
先问屏幕列表的话,已被封禁的 mina 的第 7 条消息不是被忽略而是被算作刷屏,3 1 会变成 4 0。

Pythonmain.py · 28 行
import sys lines = 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 2000 static 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)        {            返回 真;        }    }    返回 假;}