"Competencia de algoritmos · Realice rápidamente 300 preguntas" Una pregunta por día: "Emparejamiento de dulces"

" Competencia de algoritmos: 300 preguntas rápidas " se publicará en 2024 y es un libro de ejercicios auxiliar para la "Competencia de algoritmos" .
Todas las preguntas se colocan en el nuevo juez en línea del DO de creación propia .
Los códigos se proporcionan en tres lenguajes: C/C++, Java y Python. Los temas son principalmente temas de nivel medio a bajo y son adecuados para estudiantes principiantes y avanzados.


" Combinación de dulces ", enlace: http://oj.ecustacm.cn/problem.php?id=1735

Descripción de la pregunta

[Descripción del problema] Hay N niños y M dulces diferentes. Cada niño tiene su dulce favorito y su segundo dulce favorito.
   Dados algunos dulces, los niños harán fila para recibir los dulces, para cada niño, si su dulce favorito aún está ahí, elegirá su dulce favorito, de lo contrario elegirá su segundo dulce favorito.
   Si ninguno de los dos está presente, el niño romperá a llorar.
   Puede poner a los niños en cola en cualquier orden, pero asegúrese de que la cantidad de niños que lloran sea mínima.
   Encuentra el menor número de niños que lloran.
[Formato de entrada] La primera línea del formato de entrada
   contiene N y M. (N, M ≤ 100000)
   A continuación hay N líneas. Cada i línea contiene dos números fi y si, que representan el número de dulce favorito y el segundo favorito del i-ésimo niño.
[Formato de salida] Genere un número para representar la respuesta.
【Muestra de entrada】

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

【Muestra de salida】

1

respuesta

   Cada niño tiene un caramelo favorito y un segundo caramelo favorito, elegir uno de los dos equivale a que un niño conecte dos caramelos. Modele a los niños y los dulces como una gráfica, con los niños como bordes en la gráfica y los dulces como puntos en la gráfica. Se requiere que cada arista coincida con cada punto. ¿Cuántos puntos y aristas se pueden combinar como máximo?
   Dibuja el ejemplo como en la siguiente imagen. Los bordes en la imagen son los niños y los puntos son los dulces que les gustan a los niños. Por ejemplo, los bordes {1-2} son los caramelos 1 y 2 que le gustan al primer niño. Dibuje dos subgrafos conectados según los datos de muestra. Un subgrafo es {1,2,3,4}, que es un árbol; el otro subgrafo es {5,6,7,8}, que es un Hay un gráfico de anillo .

   Es fácil de analizar y concluir: si el subgrafo conectado es un árbol, el número de coincidencias es igual al número de aristas, o igual al número de puntos menos uno; si el subgrafo conectado es un gráfico cíclico, el número de partidos es igual al número de puntos. Por ejemplo, en la imagen de arriba, el subgrafo izquierdo es un árbol con 3 aristas, que puede satisfacer a 3 hijos; el subgrafo derecho es un gráfico en anillo con 4 puntos, que puede satisfacer a 4 hijos.
   Después de la conversión, esta pregunta es un problema de conectividad de dicho gráfico: (1) Construir el gráfico; (2) Consultar cuántos subgrafos conectados hay; (3) Para cada subgrafo, distinguir si es un árbol o un gráfico cíclico, y contar los bordes por separado y el número de puntos.
   La conectividad de gráficos se puede codificar mediante BFS, DFS y búsqueda de unión. La siguiente es una solución de búsqueda de uniones relativamente simple mediante codificación.
   Primero, lea los puntos y los bordes, y use la búsqueda de unión para procesarlos. Para los puntos que pertenecen al mismo subgrafo, sus conjuntos son los mismos. Al mismo tiempo, use el anillo para marcar si el conjunto es un gráfico cíclico.
   ¿Cómo utilizar union-find para procesar gráficos cíclicos? Al leer la arista uv compuesta por dos puntos u y v, si se encuentra que u y v se han leído y procesado antes y pertenecen a un conjunto, significa que la arista uv ha convertido el subgrafo original en un gráfico cíclico.
   Después de leer y procesar todos los puntos y aristas, los dos subgrafos que se muestran arriba se convierten en los dos conjuntos de búsqueda de unión que aparecen a continuación. El conjunto de búsqueda de unión 2 contiene los puntos {1, 2, 3, 4}, y el conjunto de búsqueda de unión 6 contiene los puntos {5, 6, 7, 8}.

   Tenga en cuenta dos puntos clave:
   (1) El conjunto de búsqueda de unión debe comprimirse con rutas, de modo que cada punto de un conjunto pertenezca al mismo conjunto, por ejemplo, {1, 2, 3, 4} todos pertenecen al conjunto de búsqueda de unión 2.
   (2) Es necesario marcar si cada conjunto de unión es un árbol o un gráfico cíclico. El siguiente código utiliza el parámetro de anillo para marcar si un punto está en un gráfico cíclico. Siempre que la marca del anillo de un punto en el conjunto de unión sea verdadera, el conjunto de unión es un gráfico cíclico.
   El último paso es buscar cuántos conjuntos de búsqueda de unión hay y contar cuántas coincidencias de dulces hay en cada conjunto de búsqueda de unión. Estas dos tareas se pueden completar solo con cálculos O(nlogn):
   (1) Ordene todos los conjuntos de búsqueda de unión según el tamaño del conjunto, por ejemplo, {1, 2, 3, 4}, {5, 6, 7, 8}, los conjuntos correspondientes de los dos conjuntos de búsqueda de unión son {2, 2, 2, 2}, {6, 6, 6, 6}, después de ordenar por el tamaño del conjunto, los puntos del mismo conjunto se organizan todos juntos. La cantidad calculada de clasificación es O (nlogn).
   (2) Recorra todos los conjuntos de pequeño a grande.Si los conjuntos son del mismo tamaño, pertenecen a un conjunto. Cuente el número de coincidencias de dulces dentro de este conjunto. La cantidad de cálculo es O (n).
[Punto clave] La conectividad del gráfico.

código C ++

  

#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;
}

código java

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;
    }
}

código pitón

  

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)

Supongo que te gusta

Origin blog.csdn.net/weixin_43914593/article/details/132379048
Recomendado
Clasificación