Official

E - アルバイトのシフト割り当て / Part-Time Job Shift Assignment Editorial by admin

Claude 4.5 Opus

Overview

This is a problem of matching time slots with part-time job candidates, which is a classic problem of finding the maximum bipartite matching.

Analysis

Problem Reformulation

This problem can be organized as follows: - \(N\) “time slot” nodes on the left side - \(M\) “candidate” nodes on the right side - If candidate \(j\) can work during time slot \(i\), draw an edge \((i, j)\)

In this graph, the problem becomes selecting as many edges as possible such that each node is connected to at most one edge. This is exactly the maximum bipartite matching problem.

Issues with the Naive Approach

If we try all possible assignment patterns, we would need to examine up to \(O(M^N)\) combinations in the worst case, which is far too slow for \(N, M \leq 100\).

Solution

Efficient algorithms exist for maximum bipartite matching: - Hopcroft-Karp algorithm: Computes the result in \(O(E\sqrt{V})\) - Hungarian algorithm and simple augmenting path search can also be used

Algorithm

Basic Concept of Bipartite Matching

We find augmenting paths and increase the matching one by one.

An augmenting path is a path that starts with an edge “not in the matching,” alternates between edges “in the matching” and edges “not in the matching,” and ends with an edge “not in the matching.”

Flow of the Hopcroft-Karp Algorithm

  1. BFS Phase: Starting from unmatched left-side nodes, find the length of the shortest augmenting path
  2. DFS Phase: Process all augmenting paths of the found shortest length simultaneously and update the matching
  3. Repeat until no more augmenting paths are found
Example: N=3, M=3
Time slot 1 → Candidates 1, 2
Time slot 2 → Candidate 1
Time slot 3 → Candidates 2, 3

Maximum matching:
Time slot 1 ↔ Candidate 2
Time slot 2 ↔ Candidate 1
Time slot 3 ↔ Candidate 3
→ Answer is 3

Complexity

  • Time complexity: \(O(E\sqrt{V})\)

    • \(V = N + M\) (number of vertices), \(E = \sum K_i\) (number of edges)
    • In the worst case \(E = NM\), \(V = N + M\), so \(O(NM\sqrt{N+M})\)
    • Fast enough for \(N, M \leq 100\)
  • Space complexity: \(O(N + M + E)\)

    • \(O(E)\) for storing the adjacency list
    • \(O(N + M)\) for the matching arrays

Implementation Notes

  1. Conversion to 0-indexed: Since candidate numbers start from \(1\) in the input, convert to 0-indexed using c - 1

  2. Managing matching with two arrays:

    • match_left[i]: The candidate assigned to time slot \(i\) (-1 if unassigned)
    • match_right[j]: The time slot assigned to candidate \(j\) (-1 if unassigned)
  3. Computing distances with BFS: Calculate distances from unmatched time slots, then efficiently search for augmenting paths with DFS

  4. Pruning in DFS: Set dist[u] = inf to avoid revisiting the same node

    Source Code

def main():
    import sys
    from collections import deque
    
    input_data = sys.stdin.read().split()
    idx = 0
    N = int(input_data[idx]); idx += 1
    M = int(input_data[idx]); idx += 1
    
    # Build bipartite graph
    # Left side: time slots (0 to N-1)
    # Right side: candidates (0 to M-1)
    
    # For each time slot, store list of candidates who can work
    adj = [[] for _ in range(N)]
    
    for i in range(N):
        K = int(input_data[idx]); idx += 1
        for _ in range(K):
            c = int(input_data[idx]) - 1; idx += 1  # 0-indexed
            adj[i].append(c)
    
    # Maximum bipartite matching using Hopcroft-Karp algorithm
    # match_left[i] = candidate matched to time slot i (-1 if unmatched)
    # match_right[j] = time slot matched to candidate j (-1 if unmatched)
    
    match_left = [-1] * N
    match_right = [-1] * M
    
    def bfs():
        dist = [float('inf')] * N
        queue = deque()
        for i in range(N):
            if match_left[i] == -1:
                dist[i] = 0
                queue.append(i)
        
        found = False
        while queue:
            u = queue.popleft()
            for v in adj[u]:
                next_u = match_right[v]
                if next_u == -1:
                    found = True
                elif dist[next_u] == float('inf'):
                    dist[next_u] = dist[u] + 1
                    queue.append(next_u)
        
        return found, dist
    
    def dfs(u, dist):
        for v in adj[u]:
            next_u = match_right[v]
            if next_u == -1 or (dist[next_u] == dist[u] + 1 and dfs(next_u, dist)):
                match_left[u] = v
                match_right[v] = u
                return True
        dist[u] = float('inf')
        return False
    
    matching = 0
    while True:
        found, dist = bfs()
        if not found:
            break
        for i in range(N):
            if match_left[i] == -1:
                if dfs(i, dist):
                    matching += 1
    
    print(matching)

if __name__ == "__main__":
    main()

This editorial was generated by claude4.5opus.

posted:
last update: