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

C++で行と列を反転して2値行列の「1」の数を最大化するアルゴリズム

2値(0と1のみで構成される)行列が与えられたとき、1つの行を反転し、その後1つの列を反転することで、得られる「1」の最大数を求める問題をC++で解く方法を解説します。

問題の例

たとえば、次のような入力行列を考えてみましょう。

101
010
100

この場合、最適な行と列を選んで反転すると、出力は 8 になります。

解法のアプローチ

この問題は、全探索を行う代わりに、行ごと・列ごとの「1」の個数を事前に集計することで効率的に解けます。手順は以下の通りです。

  • n := 行列の行数

  • m := 行列の列数

  • ret := 0(答えを格納する変数)

  • サイズ n の配列 row を定義(各行に含まれる「1」の個数)

  • サイズ m の配列 col を定義(各列に含まれる「1」の個数)

  • total := 0(行列全体の「1」の総数)

  • i := 0 から n 未満の間、以下を繰り返す:

    • j := 0 から m 未満の間、以下を繰り返す:

      • row[i] := row[i] + matrix[i, j]

      • col[j] := col[j] + matrix[i, j]

      • total := total + matrix[i, j]

  • 再び i := 0 から n 未満の間、以下を繰り返す:

    • j := 0 から m 未満の間、以下を繰り返す:

      • cand := total - row[i] - col[j] + ((m - row[i]) + (n - col[j]))

      • matrix[i, j] が 0 以外の場合:

        • cand := cand + 2

      • それ以外の場合:

        • cand := cand - 2

      • ret := ret と cand のうち大きい方

  • ret を返す

考え方のポイント

行 i を反転すると、その行に含まれる row[i] 個の「1」はすべて「0」に変わり、残りの (m − row[i]) 個の「0」はすべて「1」に変わります。同様に、列 j を反転すると、col[j] 個の「1」が「0」に、(n − col[j]) 個の「0」が「1」に変わります。

ただし、セル (i, j) は行の反転と列の反転の両方の影響を受けるため、上記の計算では二重に扱われてしまいます。そこで、matrix[i][j] が 1 の場合は cand に 2 を加え、0 の場合は 2 を引いて補正します。これにより、すべての (i, j) の組み合わせについて反転後の「1」の数を正しく求められます。

計算量は前処理・探索ともに O(n × m) となり、非常に効率的です。

実装例

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

#include <bits/stdc++.h>
using namespace std;
class Solution {
   public:
   int solve(vector<vector<int>> &matrix) {
      int n = matrix.size();
      int m = matrix[0].size();
      int ret = 0;
      vector<int> row(n);
      vector<int> col(m);
      int total = 0;
      for (int i = 0; i < n; i++) {
         for (int j = 0; j < m; j++) {
            row[i] += matrix[i][j];
            col[j] += matrix[i][j];
            total += matrix[i][j];
         }
      }
      for (int i = 0; i < n; i++) {
         for (int j = 0; j < m; j++) {
            int cand = total - row[i] - col[j] + (m - row[i]) + (n -
            col[j]);
            if (matrix[i][j]) {
               cand += 2;
            }else {
               cand -= 2;
            }
            ret = max(ret, cand);
         }
      }
      return ret;
   }
};
main() {
   Solution ob;
   vector<vector<int>> v = {{1,0,1},{0,1,0},{1,0,0}};
   cout << (ob.solve(v));
}

入力

{{1,0,1},{0,1,0},{1,0,0}}

出力

8
  1. C++で行列を走査する方法:行優先トラバーサルと列優先トラバーサルの徹底解説

    行列の走査には2つの方法がある2次元行列(マトリックス)の要素を訪問する方法は、大きく分けて2種類あります。行優先(Row-wise)トラバーサルでは、1行目から順に、各行の要素を先頭のインデックスから最後のインデックスまで左から右へと訪問していきます。すべての行を処理し終えるまで、これを繰り返します。一方、列優先(Column-wise)トラバーサルでは、1列目から最終列目へ向かって、各列の要素を上から下へと順番に訪問します。インデックスの基本的な考え方2次元行列 M[i][j] において、インデックス i は行、インデックス j は列を表します。行優先トラバーサルの場合は、次の順序でアクセ

  2. C++で解くスパイラル行列 III:時計回りに全マスを訪問するアルゴリズム

    本記事では、R行C列の2次元グリッドを時計回りの渦巻き(スパイラル)状に巡回し、すべてのマスを訪問した順に座標を求める問題「スパイラル行列 III」をC++で解く方法を解説します。 問題の概要 R行C列の2次元グリッドを考えます。スタート地点は (r0, c0) で、最初は東向きに面しています。グリッドの北西の角は第1行・第1列に位置し、南東の角は最終行・最終列にあります。 私たちは時計回りの渦巻き状に歩きながら、グリッド内のすべてのマスを訪問します。途中でグリッドの境界外に出た場合でも、そのまま外側を歩き続け、後で再びグリッド内に戻ることがあります。 求めるのは、訪問した順番に並べたグリッド