"Algorithm Competition·Quickly Punch 300 Questions" One question per day: "Candy Matching"

" Algorithm Competition: 300 Quick Questions " will be published in 2024 and is an auxiliary exercise book for "Algorithm Competition" .
All questions are placed in the self-built OJ New Online Judge .
Codes are given in three languages: C/C++, Java, and Python. The topics are mainly mid- to low-level topics and are suitable for entry-level and advanced students.


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

Question description

[Problem Description] There are N children and M different candies. Every child has his or her favorite candy and second favorite candy.
   Given some candies, children will line up to receive the candies. For each child, if his favorite candy is still there, he will choose his favorite candy, otherwise he will choose his second favorite candy.
   If neither is present, the child will burst into tears.
   You can queue up the children in any order, but make sure the number of crying children is minimum.
   Find the smallest number of crying children.
[Input format] The first line of input format
   input contains N and M. (N, M ≤ 100000)
   There are N lines next. Each i line contains two numbers fi and si, which represent the i-th child’s favorite and second favorite candy numbers.
[Output format] Output a number to represent the answer.
【Input sample】

8 10
2 1
3 4
2 3
6 5
7 8
6 7
7 5
5 8

【Output sample】

1

answer

   Each child has a favorite candy and a second favorite candy. Choosing one of the two is equivalent to a child connecting two candies. Model children and candies as a graph, with children as edges on the graph and candies as points on the graph. Each edge is required to match each point. How many points and edges can be matched at most?
   Draw the example as the picture below. The edges in the picture are the children and the dots are the candies that the children like. For example, the edges {1-2} are candies 1 and 2 that the first child likes. Draw two connected subgraphs based on the sample data. One subgraph is {1,2,3,4}, which is a tree; the other subgraph is {5,6,7,8}, which is a There is a ring graph.

   It is easy to analyze and conclude: if the connected subgraph is a tree, the number of matches is equal to the number of edges, or equal to the number of points minus one; if the connected subgraph is a cyclic graph, the number of matches is equal to the number of points. For example, in the picture above, the left subgraph is a tree with 3 edges, which can satisfy 3 children; the right subgraph is a ring graph with 4 points, which can satisfy 4 children.
   After conversion, this question is a connectivity problem of such a graph: (1) Construct the graph; (2) Query how many connected subgraphs there are; (3) For each subgraph, distinguish whether it is a tree or a cyclic graph, and count the edges separately and the number of points.
   Graph connectivity can be encoded using BFS, DFS, and union lookup. The following is a relatively simple union-find solution using coding.
   First, read the points and edges, and use union lookup to process them. For points belonging to the same subgraph, their sets are the same. At the same time, use ring to mark whether the set is a cyclic graph.
   How to use union-find to process cyclic graphs? When reading the edge uv composed of two points u and v, if it is found that u and v have been read and processed before and belong to a set, it means that the edge uv has turned the original subgraph into a cyclic graph.
   After reading and processing all the points and edges, the two subgraphs shown above become the two union-find sets below. Union-find set 2 contains points {1, 2, 3, 4}, and union-find set 6 contains points {5, 6, 7, 8}.

   Please note two key points:
   (1) The union search set must be compressed with paths, so that each point in a set belongs to the same set, for example {1, 2, 3, 4} all belong to the union search set 2.
   (2) It is necessary to mark whether each union set is a tree or a cyclic graph. The following code uses the ring parameter to mark whether a point is on a cyclic graph. As long as the ring mark of a point in the union set is true, the union set is a cyclic graph.
   The last step is to search how many union-lookup sets there are, and count how many candy matches there are in each union-lookup set. These two tasks can be completed with only O(nlogn) calculations:
   (1) Sort all union-find sets according to the size of the set. For example, {1, 2, 3, 4}, {5, 6, 7, 8}, the corresponding sets of the two union-find sets are {2, 2 , 2, 2}, {6, 6, 6, 6}, after sorting by the size of the set, the points in the same set are all arranged together. The calculation amount of sorting is O(nlogn).
   (2) Traverse all sets from small to large. If the sets are of the same size, they belong to a set. Count the number of candy matches within this set. The amount of calculation is O(n).
[Key point] The connectivity of the graph.

C++ code

  

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
struct child {
    
    int s; bool ring;} c[N];         //s: 并查集;  ring:这个点是否在一个有环图上
bool cmp(struct child a, struct child b){
    
     return a.s < b.s;}    //按s排序
int find_set(int x){
    
                               //并查集:查询
    if(c[x].s!=x)   c[x].s=find_set(c[x].s);   //路径压缩
    return c[x].s;
}
int main(){
    
    
    int n,m;    cin>>n>>m;
    for(int i=1;i<=m;i++)  c[i].s = i,c[i], c[i].ring = false;       //并查集初始化
    for(int i=1;i<=n;i++) {
    
    
        int u,v;  cin>>u>>v;                 //读取一条边上的两个点
        u = find_set(u);                     //查询它们的集
        v = find_set(v);
        if(u==v){
    
                    //已经是同一个集,说明这个集是一个有环图
            c[u].ring = true;    //标注这个集是一个有环图
            continue;            //已经在一个集中了,不用合并
        }
        c[v].s = c[u].s;         //u、v还不在一个集中,进行并查集合并
    }
    for(int i=1;i<=m;i++)
        find_set(i);                 //利用查询进行路径压缩,使同一个集的点的所属的集相同
    sort(c+1,c+m+1,cmp);             //对集排序,让同一个集的点排在一起
    int tot = 0;                     //统计能满足多少小朋友
    for(int i=2;i<=m;i++) {
    
              //遍历有多少个集
        bool Ring = false;           //这个集是否为有环图,初始化为非环图
        int point = 1;               //统计这个集表示的连通子图内有多少个点
        while(c[i].s == c[i-1].s) {
    
        //如果两点的集s相同,说明它们属于同一个子图
            if(c[i-1].ring || c[i].ring )  Ring = true;  //这个集是一个有环图
            point++;                   //统计这个集合的点的数量            
i++;                       //遍历这个集
        }
        if(Ring==false) point--;      //不是有环图,是一棵树
        tot += point;
    }
    cout<<n-tot;          //不能满足的小朋友人数
    return 0;
}

Java code

import java.util.*;
public class Main {
    
    
    static class Child {
    
    
        int s;
        boolean ring;
        public Child(int s, boolean ring) {
    
    
            this.s = s;
            this.ring = ring;
        }
    }
    static Child[] c;
    static int n, m;
    public static void main(String[] args) {
    
    
        Scanner scanner = new Scanner(System.in);
        n = scanner.nextInt();
        m = scanner.nextInt();
        int N = 100010;
        c = new Child[N + 1];
        for (int i = 1; i <= N; i++)   c[i] = new Child(i, false);
        for (int i = 1; i <= n; i++) {
    
    
            int u = scanner.nextInt();
            int v = scanner.nextInt();
            u = findSet(u);
            v = findSet(v);
            if (u == v) {
    
    
                c[u].ring = true;
                continue;
            }
            c[v].s = c[u].s;
        }
        for (int i = 1; i <= m; i++)       findSet(i);
        Arrays.sort(c, 1, m + 1, new Comparator<Child>() {
    
    
            public int compare(Child a, Child b) {
    
     return a.s - b.s; }
        });
        int tot = 0;
        for (int i = 2; i <= m; i++) {
    
    
            boolean ring = false;
            int point = 1;
            while (c[i].s == c[i - 1].s) {
    
    
                if (c[i - 1].ring || c[i].ring)   ring = true;
                point++;
                i++;
            }
            if (!ring)   point--;
            tot += point;
        }
        System.out.println(n - tot);
    }
    static int findSet(int x) {
    
    
        if (c[x].s != x)   c[x].s = findSet(c[x].s);
        return c[x].s;
    }
}

Python code

  

import sys
sys.setrecursionlimit(1000000)
import functools
N = 100010
class Child:
    def __init__(self, s, ring):
        self.s = s
        self.ring = ring
def cmp(a, b):   return a.s - b.s
def find_set(x):
    if c[x].s != x:  c[x].s = find_set(c[x].s)
    return c[x].s
c = []
n, m = map(int, input().split())
for i in range(N):  c.append(Child(i, False))
for i in range(1,n+1):
    u, v = map(int, input().split())
    u = find_set(u)
    v = find_set(v)
    if u == v:
        c[u].ring = True
        continue
    c[v].s = c[u].s
for i in range(1, m + 1):  find_set(i)
c[1:] = sorted(c[1:], key=functools.cmp_to_key(cmp))
tot = 0
i = 2
while i <= m:
    Ring = False
    point = 1
    while c[i].s == c[i - 1].s:
        if c[i - 1].ring or c[i].ring:  Ring = True
        point += 1
        i += 1
    if not Ring:  point -= 1
    tot += point
    i += 1
print(n - tot)

Guess you like

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