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

C++で最大スコアとなる経路の数を求める方法

問題の概要

文字が書かれた正方形のマス目(ボード)を考えます。スタート地点は右下の「S」とマークされたマスで、ゴールは左上の「E」とマークされたマスです。それ以外のマスには、1〜9の数字か、障害物を表す「X」が書かれています。1回の移動では、移動先に障害物がない場合に限り、「上」「左」「左上」のいずれかに進むことができます。

求めるのは、次の2つの要素からなるリストです。

  • 通過したマスの数字を集めたときに得られる最大の合計値
  • その最大合計を実現する経路の本数

答えは 10^9 + 7 で割った余りとして返します。経路が1つも存在しない場合は [0, 0] を返します。

例として、board = ["E12", "1X1", "21S"] が入力された場合、出力は [1, 2] となります。

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

この問題は動的計画法(DP)で効率よく解けます。dp[i][j][0] にはマス (i, j) からゴールまでの最大スコアを、dp[i][j][1] にはそのスコアを達成する経路数を格納します。具体的な手順は以下の通りです。

  1. n を行数、m を列数とします。
  2. n × m × 2 のサイズを持つ3次元配列 dp を定義します。
  3. ゴール地点を dp[n-1][m-1][0] = 0、dp[n-1][m-1][1] = 1 で初期化します。
  4. 最下行について、i を m-2 から 0 まで減らしながら処理します。
    • b[n-1][i] が 'X' の場合はループを中断します。
    • dp[n-1][i][0] = b[n-1][i] - '0' + dp[n-1][i+1][0]
    • dp[n-1][i][1] += dp[n-1][i+1][1]
  5. 最終列についても、i を n-2 から 0 まで減らしながら同様に処理します。
  6. 残りのすべてのマスについて、i を n-2 から 0 へ、j を m-2 から 0 へ減らしながら処理します。
    • b[i][j] が 'X' の場合はスキップします。
    • dp[i][j][0] を設定します('E' なら 0、それ以外なら b[i][j] - '0')。
    • maxVal を dp[i][j+1][0]、dp[i+1][j][0]、dp[i+1][j+1][0] の最大値とします。
    • maxVal が 0 で、かつ隣接する3つのマスのどこにも 'S' がない場合は、そのマスへ到達できないため dp[i][j][0] = 0 として次の反復へ進みます。
    • dp[i][j][0] += maxVal とします。
    • 隣接マスの dp スコアが maxVal と一致している場合、それぞれの経路数 dp[*][*][1] を加算します。
    • 最後に dp[i][j][1] と dp[i][j][0] を m(= 10^9 + 7)で割った余りに更新します。
  7. 最後に dp[0][0](最大スコアと経路数のペア)を返します。

C++での実装例

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

#include <bits/stdc++.h>
using namespace std;
void print_vector(vector<auto> v){
    cout << "[";
    for(int i = 0; i<v.size(); i++){
       cout << v[i] << ", ";
    } cout << "]"<<endl;
}
typedef long long int lli;
const lli m = 1e9 + 7;
lli add(lli a, lli b){
    return ((a % m) + (b % m) % m);
} class Solution {
    public:
    vector<int> pathsWithMaxScore(vector<string>& b) {
       int n = b.size();
       int m = b[0].size();
       vector < vector < vector <int> > > dp(n, vector < vector
       <int> >(m, vector <int> (2)));
       dp[n - 1][m - 1][0] = 0;
       dp[n - 1][m - 1][1] = 1;
       for(int i = m - 2; i >= 0; i--){
          if(b[n - 1][i] == 'X')break;
          dp[n - 1][i][0] = b[n - 1][i] - '0' + dp[n - 1][i + 1]
          [0];
          dp[n - 1][i][1] += dp[n - 1][i + 1][1];
       }
       for(int i = n - 2; i >= 0; i--){
          if(b[i][m - 1] == 'X')break;
          dp[i][m - 1][0] = b[i][m - 1] - '0' + dp[i + 1][m - 1]
          [0];
          dp[i][m - 1][1] += dp[i + 1][m - 1][1];
       }
       for(int i = n - 2; i >= 0; i--){
          for(int j = m - 2; j >= 0; j--){
             if(b[i][j] == 'X')continue;
             dp[i][j][0] = b[i][j] == 'E' ? 0 :b[i][j] - '0';
             int maxVal = max({dp[i][j + 1][0], dp[i + 1][j][0],
             dp[i + 1][j + 1][0]});
             if(maxVal == 0 && (b[i+1][j] != 'S' && b[i][j + 1] !
             = 'S' && b[i+1][j + 1] != 'S')){
                dp[i][j][0] = 0;
                continue;
             }
             dp[i][j][0] += maxVal;
             if(dp[i + 1][j][0] == maxVal){
                dp[i][j][1] += dp[i + 1][j][1];
             }
             if(dp[i + 1][j + 1][0] == maxVal){
                dp[i][j][1] += dp[i + 1][j + 1][1];
             }
             if(dp[i][j + 1][0] == maxVal){
                dp[i][j][1] += dp[i][j + 1][1];
             }
             dp[i][j][1] %= m;
             dp[i][j][0] %= m;
          }
       }
       return dp[0][0];
    }
};
main(){
    Solution ob;
    vector<string> v = {"E12","1X1","21S"};
    print_vector(ob.pathsWithMaxScore(v));
}

入力

{"E12","1X1","21S"}

出力

[1, 2]
  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 以下の図から出力を確認できます。 この問題は数列に関するものなので、解法ではまず数列のパターンを見つけることから始めます。 解法のアプローチ このプログラムでは、数列の