{ }codelper
图(邻接表)最大值/最小值P0293

共同好友最多的人

某个社团有 n 个人,编号为 1 到 n。
给出 m 行好友关系。
一行 "a b" 表示 a 号和 b 号互为好友。
同一个关系可能出现在多行中,也可能以 "a b" 和 "b a" 这样调换顺序的形式出现。
不管出现几次,两人都只是一对好友。
现在要给 x 号推荐一位新好友。
候选人是既不是 x 号本人、也不是 x 号好友的人。
候选人 y 的分数是既是 x 号的好友、又是 y 号的好友的人数。
在分数至少为 1 的候选人中,推荐分数最高的人。
分数相同时推荐编号最小的人。
给出 q 个询问。
每个询问给出一个 x,同一个人可能被问多次。
对每个询问输出一行:推荐的人的编号和他的分数,用一个空格隔开。
如果没有分数至少为 1 的候选人,这一行只输出 -1。

输入
第一行是 n、m、q,用一个空格隔开。
接下来 m 行每行一对 a 和 b,用一个空格隔开。
最后一行是 q 个询问 x,用一个空格隔开。
2 ≤ n ≤ 10,000,0 ≤ m ≤ 10,000,1 ≤ q ≤ 100,1 ≤ a, b, x ≤ n,a ≠ b。
全部是整数
输出
每个询问一行,共 q 行。
推荐的人的编号和分数,没有则输出 -1
样例 1 · stdin
7 8 4
1 2
1 3
2 4
3 4
2 5
3 5
4 6
2 1
1 6 7 2
stdout
4 2
2 1
-1
3 3
样例 2 · stdin
4 5 2
1 2
2 3
3 1
1 2
2 1
1 4
stdout
-1
-1
main.py
输入(stdin)

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

$ 就绪

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

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

把图存成每个人的好友列表(邻接表),就能用两层循环走遍“好友的好友”。
对 x 的每个好友 f,看 f 的每个好友 y 并把 y 的分数加 1,y 经过某个 f 与 x 相连的次数正好就是共同好友数。
第一个例子中 1 号的好友是 2、3,4 号和 5 号都是 2 分,所以推荐编号较小的 4 号。
陷阱一:同一个关系会出现多次(第一个例子的 1 2 和 2 1)。
直接加入列表,同一个好友会出现两次,分数也会加两次。
Python 的 set、JavaScript 和 TypeScript 的 Set、Java 和 C# 的 HashSet 只要加入就只保留一份。
C++ 先对 vector 做 sort,再用 unique 和 erase 去掉重复;C 把关系写成一个数 a * (n + 1) + b,用 qsort 排序后跳过与前一个相同的数。
Craykon 把见过的关系记在 字典() 里,用 包含 查询。
陷阱二:关系是双向的。
只把 b 加入 a 的列表而不把 a 加入 b 的列表,得到的是半张图。
陷阱三:好友的好友里一定有 x 自己(x → f → x)。
不排除的话,会把 x 推荐给 x 自己。
陷阱四:已经是好友的人也可能是好友的好友,必须从候选人中排除(第二个例子中 2 号和 3 号互为好友,但没有人可以推荐给 1 号)。
陷阱五:分数相同时取编号小的人。
遍历 Set 或字典的顺序不是编号顺序,所以要自己比较 y < best。
陷阱六:每个询问都重复使用同一个分数数组的解法(Java、C、C++、Craykon)必须把动过的格子恢复为 0。
清空整个数组会让每个询问都走 n 格,所以把动过的人另外收集起来,只清这些格子。
Python、JavaScript、TypeScript 和 C# 每个询问都新建一个字典(dict、Map、Dictionary),所以不需要清。
不存在的键的默认值在 Python 中用 common.get(y, 0),在 JavaScript 中用 common.get(y) ?? 0,在 C# 中用 GetValueOrDefault(y) 得到。

Pythonmain.py · 28 行
import sys data = sys.stdin.read().split()n, m, q = int(data[0]), int(data[1]), int(data[2]) # 인접 목록을 집합으로: friends[p] = p의 친구들. 같은 관계가 또 나와도 집합에는 한 번만 들어간다friends = [set() for _ in range(n + 1)]for i in range(m):    a, b = int(data[3 + 2 * i]), int(data[4 + 2 * i])    friends[a].add(b)    friends[b].add(a) out = []for k in range(q):    x = int(data[3 + 2 * m + k])    # 친구의 친구를 모두 훑으며 후보마다 '함께 아는 친구' 수를 센다    common = {}    for f in friends[x]:        for y in friends[f]:            if y != x and y not in friends[x]:  # 자기 자신과 이미 친구인 사람은 후보가 아니다                common[y] = common.get(y, 0) + 1    if not common:        out.append("-1")        continue    # 점수가 큰 사람, 같으면 번호가 작은 사람    best = min(common, key=lambda y: (-common[y], y))    out.append(f"{best} {common[best]}")print("\n".join(out))
JavaScriptmain.js · 35 行
const data = require("fs").readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const [n, m, q] = data; // 인접 목록을 Set으로: friends[p] = p의 친구들. 같은 관계가 또 나와도 Set에는 한 번만 들어간다const friends = Array.from({ length: n + 1 }, () => new Set());for (let i = 0; i < m; i++) {  const a = data[3 + 2 * i];  const b = data[4 + 2 * i];  friends[a].add(b);  friends[b].add(a);} const out = [];for (let k = 0; k < q; k++) {  const x = data[3 + 2 * m + k];  // 친구의 친구를 모두 훑으며 후보마다 '함께 아는 친구' 수를 센다  const common = new Map();  for (const f of friends[x]) {    for (const y of friends[f]) {      // 자기 자신과 이미 친구인 사람은 후보가 아니다      if (y !== x && !friends[x].has(y)) common.set(y, (common.get(y) ?? 0) + 1);    }  }  // 점수가 큰 사람, 같으면 번호가 작은 사람  let best = -1;  let bestCount = 0;  for (const [y, c] of common) {    if (c > bestCount || (c === bestCount && y < best)) {      best = y;      bestCount = c;    }  }  out.push(best === -1 ? "-1" : `${best} ${bestCount}`);}console.log(out.join("\n"));
TypeScriptmain.ts · 37 行
import { readFileSync } from "node:fs"; const data: number[] = readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const [n, m, q] = data; // 인접 목록을 Set으로: friends[p] = p의 친구들. 같은 관계가 또 나와도 Set에는 한 번만 들어간다const friends: Set<number>[] = Array.from({ length: n + 1 }, () => new Set<number>());for (let i = 0; i < m; i++) {  const a = data[3 + 2 * i];  const b = data[4 + 2 * i];  friends[a].add(b);  friends[b].add(a);} const out: string[] = [];for (let k = 0; k < q; k++) {  const x = data[3 + 2 * m + k];  // 친구의 친구를 모두 훑으며 후보마다 '함께 아는 친구' 수를 센다  const common = new Map<number, number>();  for (const f of friends[x]) {    for (const y of friends[f]) {      // 자기 자신과 이미 친구인 사람은 후보가 아니다      if (y !== x && !friends[x].has(y)) common.set(y, (common.get(y) ?? 0) + 1);    }  }  // 점수가 큰 사람, 같으면 번호가 작은 사람  let best = -1;  let bestCount = 0;  for (const [y, c] of common) {    if (c > bestCount || (c === bestCount && y < best)) {      best = y;      bestCount = c;    }  }  out.push(best === -1 ? "-1" : `${best} ${bestCount}`);}console.log(out.join("\n"));
JavaMain.java · 54 行
import java.io.*;import java.util.*; public class Main {    public static void main(String[] args) throws IOException {        StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));        st.nextToken();        int n = (int) st.nval;        st.nextToken();        int m = (int) st.nval;        st.nextToken();        int q = (int) st.nval;         // 인접 목록을 HashSet으로: friends.get(p) = p의 친구들. 같은 관계가 또 나와도 한 번만 들어간다        List<Set<Integer>> friends = new ArrayList<>();        for (int p = 0; p <= n; p++) friends.add(new HashSet<>());        for (int i = 0; i < m; i++) {            st.nextToken();            int a = (int) st.nval;            st.nextToken();            int b = (int) st.nval;            friends.get(a).add(b);            friends.get(b).add(a);        }         int[] common = new int[n + 1]; // common[y] = 지금 물음에서 y와 함께 아는 친구 수        StringBuilder sb = new StringBuilder();        for (int k = 0; k < q; k++) {            st.nextToken();            int x = (int) st.nval;            Set<Integer> mine = friends.get(x);            // 친구의 친구를 모두 훑으며 후보마다 '함께 아는 친구' 수를 센다            List<Integer> touched = new ArrayList<>();            for (int f : mine) {                for (int y : friends.get(f)) {                    if (y == x || mine.contains(y)) continue; // 자기 자신과 이미 친구인 사람은 후보가 아니다                    if (common[y]++ == 0) touched.add(y);                }            }            // 점수가 큰 사람, 같으면 번호가 작은 사람            int best = -1, bestCount = 0;            for (int y : touched) {                if (common[y] > bestCount || (common[y] == bestCount && y < best)) {                    best = y;                    bestCount = common[y];                }                common[y] = 0; // 다음 물음을 위해 센 것을 지운다            }            if (best == -1) sb.append("-1\n");            else sb.append(best).append(' ').append(bestCount).append('\n');        }        System.out.print(sb);    }}
C#Main.cs · 51 行
using System;using System.Collections.Generic;using System.Text; class Program{    static void Main()    {        var data = Console.In.ReadToEnd().Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);        int n = int.Parse(data[0]), m = int.Parse(data[1]), q = int.Parse(data[2]);         // 인접 목록을 HashSet으로: friends[p] = p의 친구들. 같은 관계가 또 나와도 한 번만 들어간다        var friends = new HashSet<int>[n + 1];        for (int p = 0; p <= n; p++) friends[p] = new HashSet<int>();        for (int i = 0; i < m; i++)        {            int a = int.Parse(data[3 + 2 * i]), b = int.Parse(data[4 + 2 * i]);            friends[a].Add(b);            friends[b].Add(a);        }         var sb = new StringBuilder();        for (int k = 0; k < q; k++)        {            int x = int.Parse(data[3 + 2 * m + k]);            // 친구의 친구를 모두 훑으며 후보마다 '함께 아는 친구' 수를 센다            var common = new Dictionary<int, int>();            foreach (int f in friends[x])            {                foreach (int y in friends[f])                {                    if (y == x || friends[x].Contains(y)) continue; // 자기 자신과 이미 친구인 사람은 후보가 아니다                    common[y] = common.GetValueOrDefault(y) + 1;                }            }            // 점수가 큰 사람, 같으면 번호가 작은 사람            int best = -1, bestCount = 0;            foreach (var (y, c) in common)            {                if (c > bestCount || (c == bestCount && y < best))                {                    best = y;                    bestCount = c;                }            }            if (best == -1) sb.Append("-1\n");            else sb.Append(best).Append(' ').Append(bestCount).Append('\n');        }        Console.Write(sb);    }}
C++main.cpp · 54 行
#include <algorithm>#include <iostream>#include <vector>using namespace std; int main() {    ios::sync_with_stdio(false);    cin.tie(nullptr);    int n, m, q;    cin >> n >> m >> q;     // 인접 목록: friends[p] = p의 친구들    vector<vector<int>> friends(n + 1);    for (int i = 0; i < m; i++) {        int a, b;        cin >> a >> b;        friends[a].push_back(b);        friends[b].push_back(a);    }    // 같은 관계가 여러 번 나올 수 있다: 정렬한 다음 unique로 겹친 것을 뒤로 몰고 erase로 잘라 낸다    for (auto &list : friends) {        sort(list.begin(), list.end());        list.erase(unique(list.begin(), list.end()), list.end());    }     vector<int> common(n + 1, 0); // common[y] = 지금 물음에서 y와 함께 아는 친구 수    vector<int> touched;    for (int k = 0; k < q; k++) {        int x;        cin >> x;        const vector<int> &mine = friends[x];        // 친구의 친구를 모두 훑으며 후보마다 '함께 아는 친구' 수를 센다        touched.clear();        for (int f : mine) {            for (int y : friends[f]) {                // 자기 자신과 이미 친구인 사람은 후보가 아니다(정렬해 두었으니 이분 탐색으로 찾는다)                if (y == x || binary_search(mine.begin(), mine.end(), y)) continue;                if (common[y]++ == 0) touched.push_back(y);            }        }        // 점수가 큰 사람, 같으면 번호가 작은 사람        int best = -1, bestCount = 0;        for (int y : touched) {            if (common[y] > bestCount || (common[y] == bestCount && y < best)) {                best = y;                bestCount = common[y];            }            common[y] = 0; // 다음 물음을 위해 센 것을 지운다        }        if (best == -1) cout << "-1\n";        else cout << best << ' ' << bestCount << '\n';    }    return 0;}
Cmain.c · 65 行
#include <stdio.h>#include <stdlib.h> static int cmp_int(const void *p, const void *q) {    int a = *(const int *)p, b = *(const int *)q;    return (a > b) - (a < b);} int main(void) {    int n, m, q;    if (scanf("%d %d %d", &n, &m, &q) != 3) return 0;    // 관계 "a b"를 양쪽 방향의 수 두 개로 적는다: a * (n + 1) + b와 b * (n + 1) + a (1억 남짓이라 int에 든다)    int *key = malloc((2 * m + 1) * sizeof(int));    for (int i = 0; i < m; i++) {        int a, b;        scanf("%d %d", &a, &b);        key[2 * i] = a * (n + 1) + b;        key[2 * i + 1] = b * (n + 1) + a;    }    // 정렬하면 같은 사람의 친구가 모이고, 같은 관계는 바로 옆에 붙으니 하나만 남긴다    qsort(key, 2 * m, sizeof(int), cmp_int);    int *to = malloc((2 * m + 1) * sizeof(int));  // 친구 번호를 사람 차례로 이어 놓은 것    int *start = calloc(n + 2, sizeof(int));      // p의 친구는 to[start[p]] … to[start[p + 1] - 1]    int edges = 0;    for (int i = 0; i < 2 * m; i++) {        if (i > 0 && key[i] == key[i - 1]) continue;        to[edges++] = key[i] % (n + 1);        start[key[i] / (n + 1) + 1]++;    }    for (int p = 1; p <= n + 1; p++) start[p] += start[p - 1];     int *common = calloc(n + 1, sizeof(int));   // common[y] = 지금 물음에서 y와 함께 아는 친구 수    int *is_friend = calloc(n + 1, sizeof(int)); // is_friend[y] = 1이면 x의 친구    int *touched = malloc((n + 1) * sizeof(int));    for (int k = 0; k < q; k++) {        int x;        scanf("%d", &x);        for (int e = start[x]; e < start[x + 1]; e++) is_friend[to[e]] = 1;        // 친구의 친구를 모두 훑으며 후보마다 '함께 아는 친구' 수를 센다        int cnt = 0;        for (int e = start[x]; e < start[x + 1]; e++) {            int f = to[e];            for (int g = start[f]; g < start[f + 1]; g++) {                int y = to[g];                if (y == x || is_friend[y]) continue; // 자기 자신과 이미 친구인 사람은 후보가 아니다                if (common[y]++ == 0) touched[cnt++] = y;            }        }        // 점수가 큰 사람, 같으면 번호가 작은 사람        int best = -1, best_count = 0;        for (int i = 0; i < cnt; i++) {            int y = touched[i];            if (common[y] > best_count || (common[y] == best_count && y < best)) {                best = y;                best_count = common[y];            }            common[y] = 0; // 다음 물음을 위해 센 것을 지운다        }        for (int e = start[x]; e < start[x + 1]; e++) is_friend[to[e]] = 0;        if (best == -1) printf("-1\n");        else printf("%d %d\n", best, best_count);    }    free(key); free(to); free(start); free(common); free(is_friend); free(touched);    return 0;}
Craykonmain.cray · 79 行
첫줄 = 控制台.读数字();n = 첫줄[0];m = 첫줄[1];q = 첫줄[2]; // 인접 목록: friends[p] = p의 친구들(목록은 10,000칸까지라 번호에서 1을 빼 0번부터 둔다)friends = [];循环 (p = 0; p < n; p++){    friends.添加([]);}// 같은 관계가 또 나오면 넣지 않는다: 본 관계를 사전에 "작은 번호 * 10000 + 큰 번호"로 적어 둔다본관계 = 字典();循环 (i = 0; i < m; i++){    쌍 = 控制台.读数字();    a = 数学.较小值(쌍[0], 쌍[1]) - 1;    b = 数学.较大值(쌍[0], 쌍[1]) - 1;    열쇠 = a * 10000 + b;    如果 (!본관계.包含(열쇠))    {        본관계[열쇠] = 真;        friends[a].添加(b);        friends[b].添加(a);    }} common = 新列表(n, 0); // common[y] = 지금 물음에서 y와 함께 아는 친구 수isFriend = 新列表(n, 假); // isFriend[y] = x의 친구인가물음들 = 控制台.读数字();遍历 (물음 在 물음들){    x = 물음 - 1;    遍历 (f 在 friends[x])    {        isFriend[f] = 真;    }    // 친구의 친구를 모두 훑으며 후보마다 '함께 아는 친구' 수를 센다    touched = [];    遍历 (f 在 friends[x])    {        遍历 (y 在 friends[f])        {            // 자기 자신과 이미 친구인 사람은 후보가 아니다            如果 (y != x && !isFriend[y])            {                如果 (common[y] == 0)                {                    touched.添加(y);                }                common[y] += 1;            }        }    }    // 점수가 큰 사람, 같으면 번호가 작은 사람    best = -1;    bestCount = 0;    遍历 (y 在 touched)    {        如果 (common[y] > bestCount || common[y] == bestCount && y < best)        {            best = y;            bestCount = common[y];        }        common[y] = 0; // 다음 물음을 위해 센 것을 지운다    }    遍历 (f 在 friends[x])    {        isFriend[f] = 假;    }    如果 (best == -1)    {        控制台.输出(-1);    }    否则    {        控制台.输出(best + 1 + " " + bestCount);    }}