Official

F - 友達の輪 / Circle of Friends Editorial by admin

DeepSeek V3

Overview

This problem requires efficiently managing groups of students, answering queries about group merging and group sizes quickly.

Analysis

In a naive approach, updating the membership of all students every time a group merge is performed would take \(O(N)\) time per merge, resulting in \(O(NQ)\) overall. Under the constraints \(N, Q \leq 2 \times 10^5\), this would not run within the time limit. Therefore, an efficient data structure for managing groups is needed.

Algorithm

This problem can be solved efficiently using the Union-Find (Disjoint Set Union, DSU) data structure. Union-Find supports the following two operations efficiently: - find(x): Find the representative (root) of the group that element \(x\) belongs to - union(a, b): Merge the groups that elements \(a\) and \(b\) belong to

Additionally, we maintain an array size to track the size (number of elements) of each group. This allows us to answer group size queries simply by returning the size stored at the root.

The implementation uses the following two optimization techniques: 1. Path Compression: During the find operation, connect each visited node directly to the root, keeping the tree height small 2. Union by size: During merging, attach the smaller tree to the larger tree, keeping the tree height small

With these optimizations, each operation runs in nearly constant time \(O(\alpha(N))\) (\(\alpha\) is the inverse Ackermann function).

Complexity

  • Time complexity: \(O(Q \cdot \alpha(N))\)
  • Space complexity: \(O(N)\)

Implementation Notes

  • Implementing Union-Find using a class helps keep the code organized

  • Combining path compression and union by size ensures efficiency

  • Prepare an array size to manage group sizes, storing the size at the root node

  • To speed up input processing, use sys.stdin.read for bulk reading

  • Also accumulate output in a list and print it all at once at the end for faster output

    Source Code

class UnionFind:
    def __init__(self, n):
        self.parent = list(range(n+1))
        self.size = [1] * (n+1)
    
    def find(self, x):
        if self.parent[x] != x:
            self.parent[x] = self.find(self.parent[x])
        return self.parent[x]
    
    def union(self, a, b):
        root_a = self.find(a)
        root_b = self.find(b)
        if root_a == root_b:
            return
        if self.size[root_a] < self.size[root_b]:
            root_a, root_b = root_b, root_a
        self.parent[root_b] = root_a
        self.size[root_a] += self.size[root_b]

def main():
    import sys
    input = sys.stdin.read
    data = input().split()
    
    n = int(data[0])
    q = int(data[1])
    uf = UnionFind(n)
    
    index = 2
    output_lines = []
    for _ in range(q):
        t = data[index]; index += 1
        if t == '1':
            a = int(data[index]); b = int(data[index+1]); index += 2
            uf.union(a, b)
        else:
            x = int(data[index]); index += 1
            root = uf.find(x)
            output_lines.append(str(uf.size[root]))
    
    print("\n".join(output_lines))

if __name__ == "__main__":
    main()

This editorial was generated by deepseekv3.

posted:
last update: