C++で隣り合う絵が同じ色にならないようにN枚の絵を描く組み合わせの数を求める方法
問題の概要
この問題では、N枚の絵とM色の絵の具が与えられ、「隣り合う2枚の絵が同じ色にならないように」描く方法が全部で何通りあるかを求めます。
答えとなる値は非常に大きくなる可能性があるため、そのまま扱うのは困難です。そこで、標準的な手法として 109 + 7 を法とした剰余(モジュロ)で計算を行います。
組み合わせの数を求める公式
隣接する絵同士が同じ色にならないように塗る場合の総数は、次の式で求められます。
Ways = m × (m − 1)(n − 1)
この式の理屈はシンプルです。まず1枚目の絵には全てのM色から自由に色を選べますが、2枚目以降の各絵は「直前の絵と異なる色」でなければならないため、選べる色は常に(M − 1)色に制限されます。これをN枚分繰り返すことで上記の公式が導かれます。
入出力例
必要な入力は、絵の枚数 n と色の数 m です。
入力:
n = 5 , m = 6
出力:
3750
n=5、m=6 の場合は「6 × 54 = 3750」となり、3750通りの描き方が存在することがわかります。
C++による実装例
巨大なべき乗を効率よく計算するために、繰り返し二乗法(バイナリ法)を利用します。このアルゴリズムを使えば、べき乗の剰余を O(log n) の時間計算量で高速に求めることができます。
#include <iostream>
#include <math.h>
#define modd 1000000007
using namespace std;
// 繰り返し二乗法による冪乗剰余の計算
unsigned long power(unsigned long x,
unsigned long y, unsigned long p) {
unsigned long res = 1;
x = x % p;
while (y > 0) {
if (y & 1)
res = (res * x) % p;
y = y >> 1;
x = (x * x) % p;
}
return res;
}
// 塗り方の総数を求める関数
int ways(int n, int m) {
return power(m - 1, n - 1, modd) * m % modd;
}
int main() {
int n = 5, m = 6;
cout << "There are " << ways(n, m) << " ways";
return 0;
}実行結果
There are 3750 ways
コードのポイント
- power関数: 指数 y をビットごとに右シフトしながら処理することで、x の y 乗を p で割った余りを高速に計算します。
- ways関数: 公式「m × (m−1)(n−1)」に基づき、(m−1) の (n−1) 乗に m を掛け、最後に 109+7 で剰余を取って答えを返します。
- オーバーフロー対策: 各段階で剰余を取ることで、非常に大きな値でも安全に計算できます。
-
C++の動的計画法を用いて二分木内の互いに隣接しないノードの最大合計を求める方法
この問題では、各ノードに値が設定された二分木が与えられます。動的計画法(DP)を活用し、選択したノード同士が互いに隣接しないという条件下で、二分木のノード値の合計として考えられる最大値を求めるプログラムを作成することが課題です。 問題の詳細 二分木の中からノードの部分集合を選び、合計値を最大化します。ただし、選んだノード同士が直接的な親子関係でつながっていてはなりません。つまり、あるノードを選んだ場合、その親ノードおよび子ノードは選択できないという制約があります。 入力例 出力例 24 解説 この例では、合計に含めるノードは以下のとおりです。 8 + 5 + 9 + 2 = 24 解法のアプ
-
【C++】循環配列で隣接しない要素を選んだときの最大合計を求める方法
問題の概要本記事では、循環配列 cirArr[] が与えられたとき、「どの2つの要素も隣接して選ばない」という条件を満たす要素の最大合計を求めるプログラムをC++で作成します。問題の詳細循環配列に対して、隣接する要素を同時に選ぶことができない、つまり要素を一つ飛ばしで選択した場合の最大合計を求める必要があります。循環配列とは、配列の末尾の要素が先頭の要素につながっている特殊な配列構造のことです。具体例で問題を確認しましょう。入力例cirArr[] = {4, 1, 5, 3, 2}出力例9解説最大の合計となる循環部分列は [4, 5, 2] で、その合計は 9 になります。解決アプローチこの問