C++で2つの異なる集合から1つ以上のペアを選択する方法
この問題では、それぞれの集合の要素数を表す2つの正整数 n と m(n ≤ m)が与えられます。求めたいのは、これら2つの集合の要素から1つ以上のペアを選択する方法の総数です。
まず、具体例で問題を確認しましょう。
入力例
2 2
出力例
6
解説
2つの集合 A と B は、どちらも2つの要素を持っています。
Set A = {1, 2}
Set B = {3, 4}ペアを1つだけ選ぶ場合は、(1, 3)、(1, 4)、(2, 3)、(2, 4) の 4通り。
ペアを2つ同時に選ぶ場合は、「(1, 3) と (2, 4)」「(1, 4) と (2, 3)」の 2通りあります。
したがって、答えは 4 + 2 = 6通りとなります。
解法のアプローチ
この問題は、順列と組み合わせの公式を組み合わせることで効率的に解けます。「i 個のペアを選ぶ」場合について考えてみましょう。
- 集合Aから i 個の要素を選び、順番を付ける方法:nPi 通り
- 集合Bから i 個の要素を選び、順番を付ける方法:mPi 通り
- 同じペアの組が i! 回重複して数えられるため、最後に i! で割る
これを i = 1 から n まですべて足し合わせると、答えが得られます。
選び方 = Σ(i=1〜n) nCi × mCi × i!
= Σ(i=1〜n) ( nPi × mPi ) / i!非常に大きな数を扱うため、計算結果は 109 + 7 で割った余りで管理します。また、割り算の代わりにフェルマーの小定理を利用して i! のモジュラ逆元を求めるのがポイントです。
C++での実装例
#include <iostream>
using namespace std;
int* fact, *inverseMod;
const int mod = 1e9 + 7;
int power(int x, int y, int p){
int res = 1;
x = x % p;
while (y) {
if (y & 1)
res = (1LL * res * x) % p;
y = y >> 1;
x = (1LL * x * x) % p;
}
return res;
}
void calculate(int n){
fact[0] = inverseMod[0] = 1;
for (int i = 1; i <= n; ++i) {
fact[i] = (1LL * fact[i - 1] * i) % mod;
inverseMod[i] = power(fact[i], mod - 2, mod);
}
}
int nPr(int a, int b) {
return (1LL * fact[a] * inverseMod[a - b]) % mod;
}
int selectPairCount(int n, int m){
fact = new int[m + 1];
inverseMod = new int[m + 1];
calculate(m);
int ans = 0;
for (int i = 1; i <= n; ++i) {
ans += (1LL * ((1LL * nPr(n, i)
* nPr(m, i)) % mod)
* inverseMod[i]) % mod;
if (ans >= mod)
ans %= mod;
}
return ans;
}
int main() {
int n = 2, m = 2;
cout<<"The number of ways to select pairs is : "<<selectPairCount(n, m);
return 0;
}実行結果
The number of ways to select pairs is : 6
コードのポイント
- power関数:繰り返し二乗法により、xy mod p を高速に計算します。
- calculate関数:階乗(fact)とそのモジュラ逆元(inverseMod)を事前に一括計算しておきます。
- nPr関数:順列 nPk を「fact[n] × inverseMod[n−k]」として即座に求められます。
- selectPairCount関数:公式に沿って i = 1 から n まで答えを累積していきます。
この実装では、階乗の前計算に O(m)、メインの集計ループに O(n) しかかからないため、全体の時間計算量は O(n + m) と非常に効率的です。競技プログラミングでも頻出のテクニックなので、ぜひ覚えておきましょう。
-
C++でグラフに長さkを超える単純パスが存在するか判定するアルゴリズム
概念 重み付きグラフ、グラフ内の始点となる頂点、そして数値k(kは始点から終点までのパスの長さを表します)が与えられたとき、与えられた始点から始まり、任意の他の頂点(終点)で終わる単純パス(閉路を含まないパス)が存在するかどうかを判定することが課題です。以下のグラフを使って説明します。 入力例1 始点 s = 0, k = 64 出力 True この場合、0 -> 7 -> 1 -> 2 -> 8 -> 6 -> 5 -> 3 -> 4 という合計距離68の単純パスが存在し、64を超えているためtrueとなります。 入力例2 始点 s = 0
-
C++で3つ以上の数値(または配列)の最大公約数(GCD)を求める方法
本記事では、3つ以上の数値の最大公約数(GCD)をC++で求める方法を解説します。2つの数値のGCDを求めるのは簡単ですが、3つ以上の数値を扱う場合はGCDの結合法則を利用します。例えば、{w, x, y, z} のGCDを求めたい場合、以下のように段階的に計算します。まず {gcd(w, x), y, z} を計算次に {gcd(gcd(w, x), y), z) を計算最後に {gcd(gcd(gcd(w, x), y), z)} を計算この手法を配列に適用すれば、任意の個数の数値に対してGCDを簡単に求めることができます。アルゴリズムgcd(a, b)begin