「アルゴリズム大会・早抜き300問」 1日1問「キャンディー合わせ」

アルゴリズム コンペティション: クイック 300 問」は 2024 年に出版される予定で、「アルゴリズム コンペティション」の補助問題集です。
すべての質問は、自作の OJ New Online Judgeに配置されます。
コードは C/C++、Java、Python の 3 つの言語で提供されており、トピックは主に中レベルから低レベルのトピックであり、初級レベルから上級レベルの学生に適しています。


キャンディーマッチング 」、リンク: http://oj.ecustacm.cn/problem.php?id=1735

質問の説明

[問題の説明] N 人の子供と M 個の異なるキャンディーが存在します。どの子供にも、一番好きなキャンディーと 2 番目に好きなキャンディーがあります。
   いくつかのキャンディーが与えられると、子供たちはキャンディーを受け取るために列に並び、自分の好きなキャンディーがまだ残っている場合は一番好きなキャンディーを選択し、そうでない場合は 2 番目に好きなキャンディーを選択します。
   どちらも存在しない場合、子供は泣き出すでしょう。
   子どもたちを任意の順序で列に並べることができますが、泣く子どもの数が最小限になるようにしてください。
   泣いている子供の最小数を見つけます。
[入力形式]入力形式
   input の 1 行目には N と M が含まれます。(N, M ≤ 100000)
   次に N 行あり、各 i 行には 2 つの数字 fi と si が含まれており、これらは i 番目の子供のお気に入りのキャンディーの番号と 2 番目に好きなキャンディーの番号を表します。
【出力形式】答えを表す数値を出力します。
【入力サンプル】

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

【出力サンプル】

1

答え

   子どもたちはそれぞれ好きなキャンディーと 2 番目に好きなキャンディーを持っており、その 2 つのうち 1 つを選択することは、子供が 2 つのキャンディーをつなげることに相当します。子供とキャンディーをグラフとしてモデル化します。子供たちはグラフ上のエッジ、キャンディーはグラフ上の点となります。各ポイントを一致させるには各エッジが必要です。最大でいくつのポイントとエッジを一致させることができますか?
   下の図のように例を描きます。絵の端は子供たち、点は子供たちが好きなキャンディーです。たとえば、エッジ {1-2} は、最初の子供が好きなキャンディー 1 と 2 です。サンプル データに基づいて、接続された 2 つのサブグラフを描画します。1 つのサブグラフは {1,2,3,4} で、これはツリーです。もう 1 つのサブグラフは、{5,6,7,8} で、これはリング グラフです。 。

   分析して結論を​​下すのは簡単です: 接続されたサブグラフがツリーの場合、一致の数はエッジの数に等しいか、点の数から 1 を引いたものに等しくなります; 接続されたサブグラフが循環グラフの場合、一致の数はエッジの数に等しくなります。一致はポイントの数と同じです。たとえば、上の図では、左側のサブグラフは 3 つの子を満たすことができる 3 つのエッジを持つツリーであり、右側のサブグラフは 4 つの子を満足できる 4 つの点を持つリング グラフです。
   変換後、この質問はそのようなグラフの接続性の問題です: (1) グラフを構築します; (2) 接続されたサブグラフがいくつあるかをクエリします; (3) 各サブグラフについて、それがツリーであるか循環グラフであるかを区別します。エッジと点の数を個別に数えます。
   グラフの接続性は、BFS、DFS、およびユニオン ルックアップを使用してエンコードできます。以下は、コーディングを使用した比較的単純な Union-Find ソリューションです。
   まず、点とエッジを読み取り、和集合ルックアップを使用してそれらを処理します。同じサブグラフに属する点については、それらのセットは同じです。同時に、リングを使用して、セットが循環グラフであるかどうかをマークします。
   Union-find を使用して循環グラフを処理するにはどうすればよいですか? 2 つの点 u と v からなるエッジ uv を読み取ったときに、u と v が以前に読み込まれて処理されており、セットに属していることが判明した場合、エッジ uv が元の部分グラフを循環グラフに変換したことを意味します。
   すべてのポイントとエッジを読み取って処理した後、上に示した 2 つのサブグラフは、以下の 2 つの和集合検索セットになります。結合検索セット 2 には点 {1, 2, 3, 4} が含まれ、結合検索セット 6 には点 {5, 6, 7, 8} が含まれます。

   2 つの重要な点に注意してください:
   (1) 集合内の各点が同じセットに属するように、和集合検索セットはパスで圧縮する必要があります。たとえば、{1、2、3、4} はすべて和集合検索セットに属します。 2.
   (2) 各和集合がツリーであるか循環グラフであるかをマークする必要があります。次のコードは、リング パラメーターを使用して、点が循環グラフ上にあるかどうかをマークします。和集合内の点のリング マークが true である限り、和集合は循環グラフです。
   最後のステップは、存在する共用体ルックアップ セットの数を検索し、各共用体ルックアップ セットに存在するキャンディーの一致の数をカウントすることです。これら 2 つのタスクは、O(nlogn) 回の計算のみで完了できます。
   (1) すべての共用体検索セットをセットのサイズに従ってソートします (例: {1, 2, 3, 4}、{5, 6, 7, 8}、2 つの共用体検索セットの対応するセット) {2, 2 , 2, 2}、{6, 6, 6, 6} である場合、セットのサイズでソートした後、同じセット内の点はすべて一緒に配置されます。ソートの計算量はO(nlogn)です。
   (2) 小さい集合から大きい集合まですべてたどり、同じ大きさの集合であれば集合に属します。このセット内のキャンディーの一致の数を数えます。計算量はO(n)となる。
【ポイント】グラフの接続性。

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

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

Pythonコード

  

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)

おすすめ

転載: blog.csdn.net/weixin_43914593/article/details/132379048