C++
 Computer >> コンピューター >  >> プログラミング >> C++

C++で公約数をもとにグラフの最大連結成分サイズを求める方法

問題の概要

互いに異なる正整数からなる配列 A が与えられたとします。ここで、次のようなグラフを考えてみましょう。

グラフのノード数は配列 A の長さと同じで、各ノードには A[0] から A[A のサイズ − 1] までのラベルが付いています。A[i] と A[j] が 1 より大きい共通の約数(公約数)を持つとき、A[i] と A[j] の間に辺が張られます。この課題では、グラフ内の最大連結成分のサイズを求めることが目標です。

たとえば、入力が [4, 6, 15, 35] の場合、出力は 4 になります。これは、4 と 6 が公約数 2 を、6 と 15 が公約数 3 を、15 と 35 が公約数 5 を共有しており、すべてのノードが 1 つの連結成分につながるためです。

解法のアプローチ

この問題は、Union-Find(素集合データ構造)を使うことで効率的に解けます。各要素を素因数分解し、同じ素因数を持つ要素同士を順次統合していくのがポイントです。具体的には、以下の手順で進めます。

getParent() 関数

  • parent[x] が -1 と等しい場合は、x をそのまま返します。
  • それ以外の場合は、parent[x] = getParent(parent[x]) を返します(経路圧縮によって木構造を平坦化し、探索を高速化します)。

unionn() 関数(x と y を引数に取る)

  • parX := getParent(x)、parY := getParent(y) を求めます。
  • parX と parY が等しい場合は、すでに同じグループなので何もせずに戻ります。
  • rank[parX] ≥ rank[parY] の場合は、rank[parX] := rank[parX] + rank[parY] とし、parent[parY] := parX とします。
  • それ以外の場合は、rank[parY] := rank[parY] + rank[parX] とし、parent[parX] := parY とします。

main メソッドでの処理

  • ret := 0、n := 配列 A のサイズ とします。
  • parent := サイズ n の配列を作成し、すべて -1 で初期化します。
  • rank := サイズ n の配列を作成し、すべて 1 で初期化します。
  • 「約数 → 要素インデックス」を記録するためのマップ m を定義します。
  • i := 0 から n 未満の間、i を 1 ずつ増やしながら次を実行します。
    • x := A[i] とします。
    • j := 2 から j × j ≤ x の間、j を 1 ずつ増やしながら次を実行します。
      • x mod j が 0 の場合(j が x の約数の場合):
        • m に j が既に存在すれば unionn(m[j], i) を呼び出し、存在しなければ m[j] := i と登録します。
        • 同様に、m に x / j が存在すれば unionn(m[x / j], i) を呼び出し、存在しなければ m[x / j] := i と登録します。
    • 試し割りループの後、m に x 自身が存在すれば unionn(m[x], i) を呼び出し、存在しなければ m[x] := i と登録します。
    • ret := max(ret, rank[getParent(i)]) として、現在の最大連結成分サイズを更新します。
  • 最後に ret を返します。

理解を深めるために、以下の実装例を見てみましょう。

C++ 実装例

#include <bits/stdc++.h>
using namespace std;
class Solution {
    public:
    vector<int> parent;
    vector<int> rank;
    int getParent(int x){
       if (parent[x] == -1)
       return x;
       return parent[x] = getParent(parent[x]);
    }
    void unionn(int x, int y){
       int parX = getParent(x);
       int parY = getParent(y);
       if (parX == parY)
       return;
       if (rank[parX] >= rank[parY]) {
          rank[parX] += rank[parY];
          parent[parY] = parX;
       } else {
          rank[parY] += rank[parX];
          parent[parX] = parY;
       }
    }
    int largestComponentSize(vector<int>& A) {
       int ret = 0;
       int n = A.size();
       parent = vector<int>(n, -1);
       rank = vector<int>(n, 1);
       unordered_map<int, int> m;
       for (int i = 0; i < n; i++) {
          int x = A[i];
          for (int j = 2; j * j <= x; j++) {
             if (x % j == 0) {
                if (m.count(j)) {
                   unionn(m[j], i);
                } else {
                   m[j] = i;
                }
                if (m.count(x / j)) {
                   unionn(m[x / j], i);
                } else {
                   m[x / j] = i;
                }
             }
          }
          if (m.count(x)) {
             unionn(m[x], i);
          } else {
             m[x] = i;
          }
          ret = max(ret, rank[getParent(i)]);
       }
       return ret;
    }
};
main(){
    Solution ob;
    vector<int> v = {4,6,15,35};
    cout << (ob.largestComponentSize(v));
}

入力

{4,6,15,35}

出力

4

計算量の目安

各要素に対して √x まで試し割りを行うため、時間計算量は要素数を n、配列内の最大値を M としたとき O(n・√M) 程度になります。加えて Union-Find の統合操作はほぼ定数時間で処理できるため、全体として非常に効率的です。空間計算量は parent・rank・マップの分だけ必要となり、O(n) です。

  1. C++でa+b+c=dを満たす最大のdを配列から見つける方法

    整数の集合が与えられたとき、d = a + b + c を満たす数 d を見つけ、その値を最大化することが目標です。ここで重要なのは、a、b、c、d のすべてが集合内に存在していなければならないという点です。集合の要素数は最小1個、最大1000個であり、各要素は有限の数であるものとします。例えば、集合が {2, 3, 5, 7, 12} の場合、12 = 2 + 3 + 7 と表現できるため、最大の d は 12 となります。解法のアプローチ:ハッシュテーブルを活用するこの問題は、ハッシュテーブル(ハッシュマップ)の手法を使うことで効率的に解くことができます。基本的な考え方は以下の通りです。ま

  2. 最長共通部分列(LCS)を求めるC++プログラム

    部分列とは、元の文字列から要素を取り出す際に、元の順序を保ったまま作られる列のことです。例えば、文字列「stuv」の部分列には「stu」「tuv」「suv」などがあります。長さnの文字列から作成できる部分列の数は、2n通り存在します。そのため、すべての部分列を総当たりで調べる方法は、文字列が長くなるほど計算量が爆発的に増えてしまいます。最長共通部分列(LCS)とは最長共通部分列(Longest Common Subsequence:LCS)とは、2つの文字列に共通して現れる部分列の中で、最も長いものを指します。例えば、文字列「ABCDGH」と「AEDFHR」の場合、最長共通部分列は「ADH」と