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

C++で解く「音楽プレイリストの総数」問題 ― 動的計画法による実装ガイド

問題の概要

N種類の異なる楽曲を収めたミュージックプレイヤーがあり、旅行中に合計L曲を聴きたいと考えます。このとき、次の条件をすべて満たすプレイリストを作成する必要があります。

  • すべての楽曲が少なくとも1回は再生されること
  • ある楽曲をもう一度再生できるのは、その後にK曲以上の他の楽曲が再生されてからであること

この条件を満たすプレイリストの総数を求めます。答えは非常に大きな値になる可能性があるため、109 + 7 で割った余りを返します。

たとえば、入力が N = 2、L = 3、K = 0 の場合、出力は 6 となります。これは [1,1,2]、[1,2,1]、[2,1,1]、[2,2,1]、[2,1,2]、[1,2,2] の6通りが存在するためです。

解法のアプローチ:動的計画法(DP)

この問題は動的計画法を用いて効率的に解くことができます。dp[i][j] を「i曲目まで聴いた時点で、ちょうどj種類の楽曲を使用しているプレイリストの数」と定義すると、状態遷移は次の2通りに分けられます。

  • 新しい楽曲を追加する場合: まだ使っていない曲は N − (j − 1) 曲あるので、dp[i−1][j−1] × (N − (j−1)) 通り
  • 既存の楽曲を繰り返す場合(j > K のとき): 直近K曲以内に再生した曲は選べないため、残りの j − K 曲から選択でき、dp[i−1][j] × (j − K) 通り

オーバーフローを防ぐため、剰余演算を安全に行うヘルパー関数を用意しておきます。

  • 関数 add(a, b)((a mod m) + (b mod m)) mod m を返す
  • 関数 sub(a, b)((a mod m) − (b mod m) + m) mod m を返す
  • 関数 mul(a, b)((a mod m) × (b mod m)) mod m を返す

メイン処理の流れは以下のとおりです。

  • サイズ (L + 1) × (N + 1) の2次元配列 dp を作成する
  • dp[0][0] := 1 と初期化する
  • i を 1 から L まで、j を 1 から N まで二重ループで回し、上記の漸化式に従って dp[i][j] を更新する
  • 最終的に dp[L][N] を返す

C++での実装例

以下のコードで実際の実装を確認できます。

#include <bits/stdc++.h>
using namespace std;
const int m = 1e9 + 7;
typedef long long int lli;
class Solution {
   public:
   int add(lli a, lli b){
      return ((a % m) + (b % m)) % m;
   }
   int sub(lli a, lli b){
      return ((a % m) - (b % m) + m) % m;
   }
   int mul(lli a, lli b){
      return ((a % m) * (b % m)) % m;
   }
   int numMusicPlaylists(int N, int L, int K) {
      vector < vector <int> > dp(L + 1, vector <int>(N + 1));
      dp[0][0] = 1;
      for(int i = 1; i <= L; i++){
         for(int j = 1; j <= N; j++){
            dp[i][j] = mul(dp[i - 1][j - 1], (N - (j - 1)));
            if(j > K){
               dp[i][j] = add(dp[i][j], mul(dp[i - 1][j], j - K));
            }
         }
      }
      return dp[L][N];
   }
};
main(){
   Solution ob;
   cout << (ob.numMusicPlaylists(2, 3, 0));
}

入力

2, 3, 0

出力

6

まとめ

本記事では、「全曲を最低1回再生し、同じ曲の再再生にはK曲の間隔が必要」という制約下でのプレイリストの総数を、動的計画法によって求める方法を紹介しました。時間計算量は O(L × N)、空間計算量も O(L × N) となり、制約の多いカウント問題に対するDPの典型的な適用例として学び価値の高いテーマです。

  1. C++で質素数(Frugal Number)を判定する方法【サンプルコード付き】

    この記事では、正の整数 N が与えられたときに、その数が質素数(Frugal Number)であるかどうかを判定するプログラムを C++ で作成する方法を解説します。 質素数とは? 質素数(FRUGAL NUMBER)とは、その数自身の桁数が、素因数分解による表現の桁数よりも厳密に大きい数のことです。 例:625 の場合 625 を素因数分解すると 54 となります。 625 自身の桁数:3 桁 54 の表現の桁数:2 桁 3 は 2 よりも厳密に大きいため、625 は質素数です。 最初のいくつかの質素数:125、128、243、256、343、512、625 など 問題を理解するための具

  2. C++で五胞体数(ペンタトープ数)を求める方法

    五胞体数とは? 五胞体数(ペンタトープ数)は、パスカルの三角形の第5の対角線上に現れる数列として知られています。この数列を定義するには、パスカルの三角形に少なくとも5つの数が必要となるため、数列の最初の数はパスカルの三角形の第4行である 1 4 6 4 1 から始まります。 本チュートリアルでは、n番目の五胞体数を求める方法を解説します。まずは具体的な例を見てみましょう。 入力 : 1出力 : 1入力 : 4出力 : 35 以下の図から出力を確認できます。 この問題は数列に関するものなので、解法ではまず数列のパターンを見つけることから始めます。 解法のアプローチ このプログラムでは、数列の