倉庫に小包が n 個、順に届く。 小包には届いた順に 1 番から番号をつける。 小包にはそれぞれ名札が一つと、中に入っている品物の番号 k 個が順に書かれている。 二つの小包が「同じ」とは、名札が同じで、品物の番号の数が同じで、位置ごとに品物の番号が同じということである。 二つの小包が「中身が同じ」とは、名札が同じで、品物の番号を順番に関係なく見たとき、同じ番号が同じ個数だけ入っているということである。 言いかえると、両方の品物の番号を小さい順に並べると同じになる。 同じ二つの小包は中身も同じである。 品物が一つもない小包どうしは、名札が同じなら同じである。 一行目に n、続く n 行に 1 番から順に小包が「名札 k 番号1 … 番号k」で与えられる。 小包ごとに一行に二つの数 a と b を空白一つで区切って出力せよ。 a はその小包と同じ小包のうち最も早く届いたものの番号、b は中身が同じ小包のうち最も早く届いたものの番号である。 その小包自身も数に入れる。 最後の行には二つの数を空白一つで区切って出力せよ。 一つ目は、同じ小包どうしを一種類と数えたときに小包が全部で何種類あるか、二つ目は、中身が同じ小包どうしを一種類と数えたときに全部で何種類あるかである。
入力
1 ≤ n ≤ 5,000、0 ≤ k ≤ 10、0 ≤ 品物の番号 ≤ 10⁹。 名札は英小文字 1〜10 文字。
import syslines = sys.stdin.read().split("\n")n = int(lines[0])first_same = {} # (이름표, 번호 튜플) → 가장 먼저 들어온 번호first_bag = {} # (이름표, 정렬한 번호 튜플) → 가장 먼저 들어온 번호out = []for i in range(1, n + 1): label, k, *rest = lines[i].split() codes = [int(x) for x in rest] # 목록(list)은 바뀔 수 있어서 열쇠가 못 된다. 튜플은 값으로 같고 해시도 값으로 정해진다 same = (label, tuple(codes)) bag = (label, tuple(sorted(codes))) # sorted는 새 목록을 만든다: codes는 그대로 a = first_same.setdefault(same, i) b = first_bag.setdefault(bag, i) out.append(f"{a} {b}")out.append(f"{len(first_same)} {len(first_bag)}")print("\n".join(out))
JavaScriptmain.js · 20 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");const n = Number(lines[0]);// Map은 열쇠를 SameValueZero로 비교한다: 배열은 같은 배열(참조)일 때만 같다.// 그래서 값이 같으면 같아지는 문자열 열쇠로 바꾼다. 사이에 쉼표·빈칸을 넣어 경계를 지킨다const firstSame = new Map();const firstBag = new Map();const out = [];for (let i = 1; i <= n; i++) { const [label, , ...rest] = lines[i].trim().split(" "); const codes = rest.map(Number); const sameKey = `${label} ${codes.join(",")}`; // sort는 제자리에서 바꾸고 같은 배열을 돌려준다: 복사본([...codes])을 정렬한다. // 비교 함수가 없으면 문자열로 정렬하니 (a, b) => a - b를 준다 const bagKey = `${label} ${[...codes].sort((a, b) => a - b).join(",")}`; if (!firstSame.has(sameKey)) firstSame.set(sameKey, i); if (!firstBag.has(bagKey)) firstBag.set(bagKey, i); out.push(`${firstSame.get(sameKey)} ${firstBag.get(bagKey)}`);}out.push(`${firstSame.size} ${firstBag.size}`);console.log(out.join("\n"));
TypeScriptmain.ts · 25 行
import { readFileSync } from "node:fs";const lines = readFileSync(0, "utf8").split("\n");const n = Number(lines[0]);// 배열은 ===로도 Map 열쇠로도 같은 배열(참조)일 때만 같다: 값으로 같아지는 문자열 열쇠를 만든다function keyOf(label: string, codes: readonly number[]): string { return `${label} ${codes.join(",")}`; // 사이에 빈칸·쉼표를 넣어 [1, 23]과 [12, 3]을 가른다}const firstSame = new Map<string, number>();const firstBag = new Map<string, number>();const out: string[] = [];for (let i = 1; i <= n; i++) { const [label, , ...rest] = lines[i].trim().split(" "); const codes: readonly number[] = rest.map(Number); const sorted = [...codes].sort((a, b) => a - b); // 복사본을 정렬한다(readonly라 codes.sort는 타입 오류) const sameKey = keyOf(label, codes); const bagKey = keyOf(label, sorted); if (!firstSame.has(sameKey)) firstSame.set(sameKey, i); if (!firstBag.has(bagKey)) firstBag.set(bagKey, i); out.push(`${firstSame.get(sameKey)} ${firstBag.get(bagKey)}`);}out.push(`${firstSame.size} ${firstBag.size}`);console.log(out.join("\n"));
JavaMain.java · 36 行
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;public class Main { // record는 equals·hashCode를 멤버마다 equals로 만들어 준다. // List.equals는 길이와 자리마다의 값을 비교한다(배열 int[]라면 같은 배열인지만 본다) record Key(String label, List<Integer> codes) {} public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); int n = Integer.parseInt(br.readLine().trim()); Map<Key, Integer> firstSame = new HashMap<>(); Map<Key, Integer> firstBag = new HashMap<>(); StringBuilder out = new StringBuilder(); for (int i = 1; i <= n; i++) { String[] p = br.readLine().trim().split(" "); List<Integer> codes = new ArrayList<>(); for (int j = 2; j < p.length; j++) codes.add(Integer.parseInt(p[j])); List<Integer> sorted = new ArrayList<>(codes); // 복사본을 정렬한다 sorted.sort(null); // putIfAbsent: 열쇠가 없으면 i를 넣고 null을, 있으면 먼저 넣은 값을 돌려준다 Integer a = firstSame.putIfAbsent(new Key(p[0], codes), i); Integer b = firstBag.putIfAbsent(new Key(p[0], sorted), i); if (a == null) a = i; if (b == null) b = i; out.append(a).append(' ').append(b).append('\n'); } out.append(firstSame.size()).append(' ').append(firstBag.size()).append('\n'); System.out.print(out); }}
C#Main.cs · 32 行
using System;using System.Collections.Generic;using System.Linq;using System.Text;class Program{ static void Main() { var input = Console.In; int n = int.Parse(input.ReadLine()!.Trim()); // List<int>는 Equals가 참조 비교라 열쇠로 쓰면 값이 같아도 다른 열쇠가 된다. // 값으로 같아지는 문자열 열쇠를 만든다(사이에 빈칸·쉼표를 넣어 경계를 지킨다) var firstSame = new Dictionary<string, int>(); var firstBag = new Dictionary<string, int>(); var sb = new StringBuilder(); for (int i = 1; i <= n; i++) { string[] p = input.ReadLine()!.Trim().Split(' '); string label = p[0]; int[] codes = p.Skip(2).Select(int.Parse).ToArray(); int[] sorted = codes.OrderBy(x => x).ToArray(); // 새 배열: codes는 그대로 string sameKey = label + " " + string.Join(",", codes); string bagKey = label + " " + string.Join(",", sorted); if (!firstSame.TryGetValue(sameKey, out int a)) firstSame[sameKey] = a = i; if (!firstBag.TryGetValue(bagKey, out int b)) firstBag[bagKey] = b = i; sb.Append(a).Append(' ').Append(b).Append('\n'); } sb.Append(firstSame.Count).Append(' ').Append(firstBag.Count).Append('\n'); Console.Write(sb); }}
C++main.cpp · 33 行
#include <algorithm>#include <iostream>#include <map>#include <string>#include <utility>#include <vector>int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); int n; std::cin >> n; // std::string과 std::vector는 ==와 <가 값(길이와 자리마다의 원소)으로 비교한다. // 그래서 pair<string, vector<int>>를 그대로 map의 열쇠로 쓸 수 있다 using Key = std::pair<std::string, std::vector<int>>; std::map<Key, int> firstSame, firstBag; std::string out; for (int i = 1; i <= n; i++) { std::string label; int k; std::cin >> label >> k; std::vector<int> codes(k); for (int &x : codes) std::cin >> x; std::vector<int> sorted = codes; // vector 대입은 복사: codes는 그대로 std::sort(sorted.begin(), sorted.end()); // emplace는 열쇠가 이미 있으면 넣지 않고, 있던 칸을 가리키는 반복자를 돌려준다 int a = firstSame.emplace(Key{label, codes}, i).first->second; int b = firstBag.emplace(Key{label, sorted}, i).first->second; out += std::to_string(a) + ' ' + std::to_string(b) + '\n'; } out += std::to_string(firstSame.size()) + ' ' + std::to_string(firstBag.size()) + '\n'; std::cout << out;}
Cmain.c · 73 行
#include <stdio.h>#include <stdlib.h>#include <string.h>#define MAXN 5000typedef struct { char label[11]; int k; int codes[10]; /* 들어온 차례 그대로 */ int sorted[10]; /* 작은 것부터 늘어놓은 것 */} Parcel;static Parcel p[MAXN];static int order[MAXN];static int cmp_int(const void *a, const void *b) { int x = *(const int *)a, y = *(const int *)b; return (x > y) - (x < y);}/* 두 꾸러미를 비교한다. 문자열은 ==로 비교하면 주소를 비교하니 strcmp로, 배열은 길이를 먼저 보고 칸마다 비교한다 */static int compare(const Parcel *a, const Parcel *b, int use_sorted) { int c = strcmp(a->label, b->label); if (c != 0) return c; if (a->k != b->k) return a->k - b->k; const int *x = use_sorted ? a->sorted : a->codes; const int *y = use_sorted ? b->sorted : b->codes; for (int j = 0; j < a->k; j++) if (x[j] != y[j]) return x[j] < y[j] ? -1 : 1; return 0;}static int use_sorted_now;static int cmp_order(const void *a, const void *b) { int i = *(const int *)a, j = *(const int *)b; int c = compare(&p[i], &p[j], use_sorted_now); return c != 0 ? c : i - j; /* 같은 것끼리는 먼저 들어온 것이 앞에 */}/* 같은 것끼리 모이도록 번호를 정렬하고, 무리마다 맨 앞(가장 먼저 들어온 것)의 번호를 적는다 */static int group(int n, int use_sorted, int *first) { for (int i = 0; i < n; i++) order[i] = i; use_sorted_now = use_sorted; qsort(order, (size_t)n, sizeof order[0], cmp_order); int kinds = 0, head = 0; for (int t = 0; t < n; t++) { if (t == 0 || compare(&p[order[t]], &p[order[t - 1]], use_sorted) != 0) { head = order[t]; kinds++; } first[order[t]] = head; } return kinds;}int main(void) { static int first_same[MAXN], first_bag[MAXN]; int n; scanf("%d", &n); for (int i = 0; i < n; i++) { scanf("%10s %d", p[i].label, &p[i].k); for (int j = 0; j < p[i].k; j++) scanf("%d", &p[i].codes[j]); memcpy(p[i].sorted, p[i].codes, sizeof p[i].codes); qsort(p[i].sorted, (size_t)p[i].k, sizeof(int), cmp_int); } int same_kinds = group(n, 0, first_same); int bag_kinds = group(n, 1, first_bag); for (int i = 0; i < n; i++) printf("%d %d\n", first_same[i] + 1, first_bag[i] + 1); printf("%d %d\n", same_kinds, bag_kinds); return 0;}