{ }codelper
遍历字典P0256

找出互相选择的搭档

为了两人一组的活动,学生们各写下一位想一起做的朋友。
第一行给出申请数 n,接下来 n 行按到达顺序给出申请 "a b",表示 a 选了 b。
同一个学生申请多次时,只保留最后一次申请。
学生的顺序是他第一次申请的顺序,重新申请也不会改变。
如果两个学生 x、y 在保留下来的申请中互相选了对方,他们就是互相的搭档。
学生不会选自己。
被选的朋友可能一次也没有申请过。
第一行输出互为搭档的对数。
接着每对输出一行 "x y",x 是两人中顺序靠前的学生。
各对按 x 的顺序输出。
没有搭档时只输出第一行的 0。

输入
1 ≤ n ≤ 5,000。
名字由 1~10 个小写英文字母组成,一次申请中的两个名字不同
输出
第一行为对数,接着每对一行(x y),按 x 的顺序
样例 1 · stdin
7
mina jun
jun sora
sora jun
dan mina
mina dan
eve kai
jun mina
stdout
1
mina dan
样例 2 · stdin
3
b a
a b
c a
stdout
1
b a
main.py
输入(stdin)

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

$ 就绪

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

其他语言怎么写?查看各语言题解 · 8 种语言题解

把最后一次申请放进字典(pick[a] = b),再按插入顺序遍历这个字典,检查 pick[b] == a 即可。
给已有的键重新赋值时,这个键仍留在第一次插入的位置(Python dict、JS/TS Map、Java LinkedHashMap、Craykon 字典的 键列表)。
只保留第一次申请的 setdefault、putIfAbsent、TryAdd 是错的。
顺序是陷阱。
Java HashMap 和 C++ unordered_map 顺序是乱的,C++ std::map 按名字的字典序遍历。
C# Dictionary 的遍历顺序文档并不保证,所以把第一次申请的名字另记在一个列表里,遍历那个列表。
被选的朋友可能没有申请过,这也是陷阱。
C# pick[b] 会抛出 KeyNotFoundException,所以用 TryGetValue;Python 用 pick.get(b);Craykon 先用 包含 询问(读取不存在的键会停止)。
C++ pick[b] 不报错,却会插入一个空值,所以用 find。
如果遍历时只检查是否互选,同一对会在 x 的顺序和 y 的顺序各出现一次,所以要给已经写过的搭档做标记。
C 没有字典,要按第一次申请的顺序记进数组,用 strcmp 从前往后找。

Pythonmain.py · 16 行
n = int(input())pick = {}for _ in range(n):    a, b = input().split()    pick[a] = b  # 다시 내면 값만 바뀌고, 열쇠의 차례(처음 넣은 차례)는 그대로다pairs = []done = set()for a, b in pick.items():  # dict는 열쇠를 처음 넣은 차례대로 돈다    if a in done:        continue    if pick.get(b) == a:        pairs.append(f"{a} {b}")        done.add(b)  # b 차례가 와도 같은 쌍을 다시 쓰지 않는다print(len(pairs))for p in pairs:    print(p)
JavaScriptmain.js · 18 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");const n = Number(lines[0]);// Map은 열쇠를 처음 넣은 차례대로 돈다. set으로 값을 바꿔도 차례는 그대로다const pick = new Map();for (let i = 1; i <= n; i++) {  const [a, b] = lines[i].trim().split(" ");  pick.set(a, b);}const pairs = [];const done = new Set();for (const [a, b] of pick) {  if (done.has(a)) continue;  if (pick.get(b) === a) {    pairs.push(`${a} ${b}`);    done.add(b);  }}console.log([String(pairs.length), ...pairs].join("\n"));
TypeScriptmain.ts · 19 行
import { readFileSync } from "node:fs";const lines: string[] = readFileSync(0, "utf8").split("\n");const n = Number(lines[0]);// Map은 열쇠를 처음 넣은 차례대로 돈다. set으로 값을 바꿔도 차례는 그대로다const pick = new Map<string, string>();for (let i = 1; i <= n; i++) {  const [a, b] = lines[i].trim().split(" ");  pick.set(a, b);}const pairs: string[] = [];const done = new Set<string>();for (const [a, b] of pick) {  if (done.has(a)) continue;  if (pick.get(b) === a) {    pairs.push(`${a} ${b}`);    done.add(b);  }}console.log([String(pairs.length), ...pairs].join("\n"));
JavaMain.java · 35 行
import java.io.BufferedReader;import java.io.InputStreamReader;import java.util.ArrayList;import java.util.HashSet;import java.util.LinkedHashMap;import java.util.List;import java.util.Map;import java.util.Set; public class Main {    public static void main(String[] args) throws Exception {        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));        int n = Integer.parseInt(br.readLine().trim());        // HashMap은 차례가 뒤섞인다. LinkedHashMap은 처음 넣은 차례를 지키고, put으로 값을 바꿔도 차례는 그대로다        Map<String, String> pick = new LinkedHashMap<>();        for (int i = 0; i < n; i++) {            String[] p = br.readLine().trim().split(" ");            pick.put(p[0], p[1]);        }        List<String> pairs = new ArrayList<>();        Set<String> done = new HashSet<>();        for (Map.Entry<String, String> e : pick.entrySet()) {            String a = e.getKey(), b = e.getValue();            if (done.contains(a)) continue;            if (a.equals(pick.get(b))) {   // b가 신청하지 않았으면 get은 null                pairs.add(a + " " + b);                done.add(b);            }        }        StringBuilder out = new StringBuilder();        out.append(pairs.size()).append('\n');        for (String s : pairs) out.append(s).append('\n');        System.out.print(out);    }}
C#Main.cs · 37 行
using System;using System.Collections.Generic;using System.Text; class Program{    static void Main()    {        int n = int.Parse(Console.ReadLine().Trim());        // Dictionary의 도는 차례는 문서가 약속하지 않으니, 처음 신청한 차례를 목록에 따로 적는다        var pick = new Dictionary<string, string>();        var order = new List<string>();        for (int i = 0; i < n; i++)        {            string[] p = Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries);            if (!pick.ContainsKey(p[0])) order.Add(p[0]);            pick[p[0]] = p[1];        }        var pairs = new List<string>();        var done = new HashSet<string>();        foreach (string a in order)        {            string b = pick[a];            if (done.Contains(a)) continue;            // pick[b]는 b가 신청하지 않았으면 KeyNotFoundException — TryGetValue로 묻는다            if (pick.TryGetValue(b, out string back) && back == a)            {                pairs.Add(a + " " + b);                done.Add(b);            }        }        var sb = new StringBuilder();        sb.Append(pairs.Count).Append('\n');        foreach (string s in pairs) sb.Append(s).Append('\n');        Console.Write(sb);    }}
C++main.cpp · 33 行
#include <iostream>#include <string>#include <unordered_map>#include <unordered_set>#include <vector> int main() {    std::ios::sync_with_stdio(false);    int n;    std::cin >> n;    // std::map은 이름의 사전 순으로, unordered_map은 뒤섞인 차례로 돈다. 처음 신청한 차례는 따로 적는다    std::unordered_map<std::string, std::string> pick;    std::vector<std::string> order;    for (int i = 0; i < n; i++) {        std::string a, b;        std::cin >> a >> b;        if (pick.find(a) == pick.end()) order.push_back(a);        pick[a] = b;    }    std::vector<std::string> pairs;    std::unordered_set<std::string> done;    for (const std::string& a : order) {        if (done.count(a)) continue;        const std::string& b = pick[a];        auto it = pick.find(b);   // pick[b]로 물으면 없는 b가 빈 값으로 생긴다        if (it != pick.end() && it->second == a) {            pairs.push_back(a + " " + b);            done.insert(b);        }    }    std::cout << pairs.size() << "\n";    for (const std::string& s : pairs) std::cout << s << "\n";}
Cmain.c · 45 行
#include <stdio.h>#include <string.h> /* C에는 사전이 없어서, 처음 신청한 차례대로 이름과 고른 친구를 배열에 적고 앞에서부터 찾는다 */#define MAXN 5000static char name[MAXN][11];static char pick[MAXN][11];static int done[MAXN];static int kinds = 0; static int find(const char *s) {    for (int i = 0; i < kinds; i++)        if (strcmp(name[i], s) == 0) return i;    return -1;} int main(void) {    int n;    char a[16], b[16];    scanf("%d", &n);    for (int i = 0; i < n; i++) {        scanf("%15s %15s", a, b);        int k = find(a);        if (k < 0) {            k = kinds++;            strcpy(name[k], a);        }        strcpy(pick[k], b);   /* 다시 내면 고른 친구만 바뀐다 */    }    int count = 0;    static int first[MAXN], second[MAXN];    for (int i = 0; i < kinds; i++) {        if (done[i]) continue;        int j = find(pick[i]);        if (j >= 0 && strcmp(pick[j], name[i]) == 0) {            first[count] = i;            second[count] = j;            count++;            done[j] = 1;        }    }    printf("%d\n", count);    for (int k = 0; k < count; k++) printf("%s %s\n", name[first[k]], name[second[k]]);    return 0;}
Craykonmain.cray · 27 行
n = 控制台.读行().转数字();pick = 字典();循环 (i = 0; i < n; i++){    p = 控制台.读行().去空白().分割(" ");    pick[p[0]] = p[1]; // 다시 내면 값만 바뀌고 열쇠의 차례는 그대로다}pairs = [];done = 字典();// 크레이콘 사전의 열쇠목록은 처음 넣은 차례다遍历 (a 在 pick.键列表){    b = pick[a];    如果 (!done.包含(a) && pick.包含(b))    {        如果 (pick[b] == a)        {            pairs.添加(a + " " + b);            done[b] = 真;        }    }}控制台.输出(pairs.数量);遍历 (s 在 pairs){    控制台.输出(s);}