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

C++で解く「K逆ペア配列」問題 ― 動的計画法による効率的な解法

問題概要

2つの整数 n と k が与えられます。1 から n までの数字をそれぞれ1回ずつ使って構成される配列のうち、逆ペア(転倒ペア)がちょうど k 個含まれるものが何通り存在するかを求めてください。ここで、逆ペアとは配列中の i 番目と j 番目の要素について、i < j かつ a[i] > a[j] が成り立つペアのことを指します。答えは非常に大きな値になる可能性があるため、109 + 7 で割った余りを返します。

例えば、n = 3、k = 1 の場合、出力は 2 になります。これは [1, 3, 2] と [2, 1, 3] の2つの配列が、それぞれちょうど1つの逆ペアを持つためです。

解法のアプローチ

この問題は動的計画法(DP)を用いて効率的に解くことができます。dp[i][j] を「長さ i の配列のうち、逆ペアがちょうど j 個含まれるものの個数」と定義します。長さ i の配列に最大値 i を挿入する位置によって、新たに 0 ~ i-1 個の逆ペアが生じるため、単純な漸化式では計算量が膨大になります。そこで累積和(スライディングウィンドウ)の考え方を活用し、漸化式を変形することで O(n × k) の計算量で求められます。

具体的な手順は以下の通りです。

  • サイズ (n + 1) × (k + 1) の2次元配列 dp を定義する
  • dp[0][0] := 1 と初期化する
  • i を 1 から n まで繰り返す:
    • dp[i][0] := 1 とする
    • j を 1 から k まで繰り返す:
      • dp[i][j] := dp[i][j - 1] + dp[i - 1][j]
      • dp[i][j] := dp[i][j] mod m
      • j >= i の場合:
        • dp[i][j] := (dp[i][j] - dp[i - 1][j - i] + m) mod m
  • dp[n][k] を返す

実装例(C++)

以下にC++による実装を示します。

#include <bits/stdc++.h>
using namespace std;
const int m = 1e9 + 7;
class Solution {
public:
    int kInversePairs(int n, int k) {
        vector < vector <int> > dp(n + 1, vector <int>(k + 1));
        dp[0][0] = 1;
        for(int i = 1; i <= n; i++){
            dp[i][0] = 1;
            for(int j = 1; j <= k; j++){
                dp[i][j] = dp[i][j - 1] + dp[i - 1][j];
                dp[i][j] %= m;
                if(j >= i){
                    dp[i][j] = (dp[i][j] - dp[i - 1][j - i] + m) % m;
                }
            }
        }
        return dp[n][k];
    }
};
main(){
    Solution ob;
    cout << (ob.kInversePairs(4,2));
}

入力

4
2

出力

5

n = 4、k = 2 の場合、逆ペアをちょうど2つ持つ配列が5通り存在するため、出力は 5 となります。

  1. C++で文字列の配列を定義・操作する方法を解説

    この記事では、C++において文字列の配列をどのように定義し、扱うのかを詳しく解説します。C言語との違い:文字列配列の基礎知識C言語には文字列型が存在しないため、文字列はchar型の配列(文字配列)として表現する必要がありました。そのため、複数の文字列をまとめて管理する「文字列の配列」を作るには、2次元のchar型配列を用意し、各行に異なる文字列を格納するという手法が取られていました。これは直感的ではなく、コードも冗長になりがちでした。一方、C++ではstd::stringクラスが標準ライブラリとして提供されています。このクラスのオブジェクトを使えば、文字列データを効率的かつ安全に格納・操作でき

  2. C++で配列を並べ替える方法|選択ソートの仕組みと実装例を解説

    C++では、さまざまなソート(並べ替え)アルゴリズムを使って配列を整列できます。ソート済みの配列とは、数値の大小順やアルファベット順など、何らかの基準に従って要素が並び替えられた配列のことです。代表的なソートアルゴリズムには、バブルソート、挿入ソート、選択ソート、マージソート、クイックソート、ヒープソートなどがあります。本記事では、その中でも構造がシンプルで理解しやすい「選択ソート」を取り上げ、実際のコード例とともに詳しく解説していきます。 選択ソートとは? 選択ソートは、未ソート部分の中から最小値を繰り返し探し出し、それを未ソート部分の先頭にある要素と交換することで、配列全体を昇順に整列さ