"Algorithm Competition · 300 Quick Questions" one question per day: "Ball Matching"

" Algorithm Competition: 300 Quick Questions " will be published in 2024, and it is an auxiliary workbook for "Algorithm Competition" .
All questions are placed in the self-built OJ New Online Judge .
The codes are provided in C/C++, Java, and Python. The topics are mainly low-to-medium level, suitable for beginners and advanced.


" Ball Matching ", link: http://oj.ecustacm.cn/problem.php?id=1850

topic description

[Title description] Given n small balls, numbered 1-n, given m baskets, numbered 1-m.
  Each ball is only allowed to fit into one of two specific baskets.
  Each ball must go into some basket.
  A basket is special if the number of balls in it is odd.
  Calculate the minimum number of special baskets.
[Input format] The first line is two positive integers n and m, 1≤n, m≤200000.
  In the next n lines, each line has two numbers Ai, Bi, indicating that the i-th ball can be put into the basket numbered by Ai or Bi.
  1≤Ai, Bi≤m, Ai≠Bi.
【Output Format】 Output a number to represent the answer.
【Input sample】

4 3
1 2
2 3
1 3
1 2

【Example of output】

0

answer

  Put n balls into m baskets, the most violent method is to list all possible ways to put them, and then look at the method with the fewest special baskets. You can use DFS encoding to find all possible permutations, but it obviously times out.
  Readers may also have thought about using DP to put small balls into the basket one by one from the first ball, and at the same time calculate the least special basket until all the small balls are placed. But the DP transfer equation seems to be impossible to write out.
  In fact, this question is a simple graph problem. Think of the baskets as points on the graph; a ball connecting two baskets can be thought of as a line connecting the points. In this way, the basket and the ball are respectively abstracted into points and lines, and all the baskets and balls form a graph, in which some points are connected and some are not. How many points on a connected subgraph are at least special baskets? It is easy to prove that if the number of lines in this subgraph is even, the number of special baskets is at least 0; if the number of lines in the subgraph is odd, the number of special baskets is at least 1. Readers are invited to prove it themselves.
  The programming of this problem is as follows: use union search to combine baskets and balls to form multiple connected subgraphs; each connected subgraph is a union search set; when merging two sets, use the root of union search to record the number of bus bars in this connected subgraph. The code only performs a union-find operation on each ball (line), and one merge is O(1), and the total complexity of n merges is O(n).
[Key points] The application of union search.

C++ code

  Be sure to use a union search set with path compression, the complexity of a merger is O(1).

#include<bits/stdc++.h>
using namespace std;
const int N = 200005;
int n,m,f[N],s[N],vis[N];      //s是并查集,f[i]是点i上的线条数量
int find(int x){
    
                   //并查集的查询,带路径压缩
    if(x!=s[x]) s[x] = find(s[x]);
    return s[x];
}
void merge(int x, int y){
    
          //合并
    int p = find(x), q = find(y);
    if (p!=q){
    
                      //原来不属于一个集合
        s[p] = s[q];            //并查集合并
        f[q] += f[p]+1;         //用并查集的根记录这个连通子图的线条总数
    }
    else f[p]++;                //用并查集的根记录这个连通子图的线条总数
}
int main(){
    
    
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++) s[i]=i;  //初始化并查集
    for (int i=1;i<=n;i++)    {
    
    
        int x,y;   scanf("%d%d",&x,&y);
        merge(x,y);
    }
    int ans = 0;
    for (int i=1;i<=m;i++){
    
    
        int x = find(i);           //查找有多少个集
        if (!vis[x]) {
    
                 //集x还没有统计过
            if (f[x] & 1)  ans++;  //集x的线条总数是奇数,答案加1
            vis[x] = 1;       
        }
    }
    printf("%d",ans);
    return 0;
}

java code

import java.util.Scanner;

public class Main {
    
    
    static int N = 200005;
    static int n,m;
    static int[] f = new int[N];     //f[i]是点i上的线条数量
    static int[] s = new int[N];      //s是并查集
    static int[] vis = new int[N];

    public static void main(String[] args) {
    
    
        Scanner scan = new Scanner(System.in);
        n = scan.nextInt();
        m = scan.nextInt();
        for (int i = 1; i <= m; i++) s[i] = i;    //初始化并查集
        for (int i = 1; i <= n; i++) {
    
    
            int x = scan.nextInt(), y = scan.nextInt();
            merge(x, y);
        }
        int ans = 0;
        for (int i = 1; i <= m; i++) {
    
    
            int x = find(i);                      //查找有多少个集
            if (vis[x] == 0) {
    
                        //集x还没有统计过
                if ((f[x] & 1) == 1) ans++;       //集x的线条总数是奇数,答案加1
                vis[x] = 1;
            }
        }
        System.out.println(ans);
        scan.close();
    }
    static int find(int x) {
    
                           //并查集的查询,带路径压缩
        if (x != s[x]) s[x] = find(s[x]);
        return s[x];
    }
    static void merge(int x, int y) {
    
     //合并
        int p = find(x), q = find(y);
        if (p != q) {
    
                                  //如果原来不属于一个集合
            s[p] = s[q];                           //并查集合并
            f[q] += f[p] + 1;                      //用并查集的根记录这个连通子图的线条总数
        }
        else f[p]++;                 //用并查集的根记录这个连通子图的线条总数
    }
}

Python code

  Pay attention to expand the stack with setrecursionlimit, because find() is a recursive function.

import sys
sys.setrecursionlimit(1000000)
N = 200005
n, m = map(int, input().split())
f, s, vis = [0] * N, [0] * N, [0] * N     # s是并查集,f[i]是点i上的线条数量
def find(x):                              # 并查集的查询,带路径压缩
    if x != s[x]:   s[x] = find(s[x])
    return s[x]
def merge(x, y):                          # 合并
    p, q = find(x), find(y)
    if p != q:                            # 如果原来不属于一个集合
        s[p] = s[q]                       # 并查集合并
        f[q] += f[p] + 1                  # 用并查集的根记录这个连通子图的线条总数
    else:  f[p] += 1                      # 用并查集的根记录这个连通子图的线条总数
for i in range(1, m + 1):   s[i] = i      # 初始化并查集
for i in range(1, n + 1):
    x, y = map(int, input().split())
    merge(x, y)
ans = 0
for i in range(1, m + 1):
    x = find(i)                           # 查找有多少个集
    if not vis[x]:                        # 集x还没有统计过
        if f[x] & 1: ans += 1             # 集x的线条总数是奇数,答案加1            
        vis[x] = 1
print(ans)

Guess you like

Origin blog.csdn.net/weixin_43914593/article/details/131800622