C++で1の数が0の数より1つ多い最大部分行列の面積を求める方法
この記事では、0と1だけで構成された n×n の2次元行列が与えられたときに、「1の個数が0の個数よりちょうど1つ多い」部分行列のうち、面積(要素数)が最大になるものを求めるC++プログラムを解説します。
問題の例
入力
bin[N][N] = {
{0, 1, 0, 0},
{1, 1, 0, 0},
{1, 0, 1, 1},
{0, 1, 0, 1}
}
出力
9
説明
部分行列: bin[1][0], bin[1][1], bin[1][2] bin[2][0], bin[2][1], bin[2][2] bin[3][0], bin[3][1], bin[3][2] これが「1の数が0の数より1つ多い」という条件を満たす 最大の部分行列です。 0の個数 = 4 1の個数 = 5
解法アプローチ
素朴な方法:全部分行列の列挙
もっとも単純な発想は、行列から取り得るすべての部分行列を列挙し、その中で条件を満たすものの最大面積を返すことです。考え方はシンプルで実装も容易ですが、多重ループのネストが必要になり、時間計算量は O(n⁴) に達します。そのため、大きな行列に対しては実用的ではありません。
効率的な方法:左右の列を固定して1次元問題へ帰着
ここで紹介するのは、より効果的な手法です。まず行列の左端と右端の列を固定し、1を +1、0を −1 として各行ごとに値を累積していきます。こうすると、「1の数が0の数より1つ多い区間」を見つける問題は、「区間和が1となる最長の部分配列」を求める1次元問題に置き換えられます。
この1次元の最長部分配列問題は、ハッシュマップ(連想配列)を使って各累積和の最初の出現位置を記録することで、線形時間 O(n) で解けます。左右の列の組み合わせは O(n²) 通りあるため、全体の時間計算量は O(n³) となり、全探索よりも大幅に高速化できます。
C++による実装例
以下は、上記の解法の動作を示すサンプルプログラムです。
#include <bits/stdc++.h>
using namespace std;
#define SIZE 10
// 区間和が1となる最長の部分配列を求める関数
int lenOfLongSubarr(int row[], int n, int& startInd, int& finishInd){
unordered_map<int, int> subArr;
int sumVal = 0, maxSubArrLen = 0;
for (int i = 0; i < n; i++) {
sumVal += row[i];
// 先頭からの累積和が1の場合
if (sumVal == 1) {
startInd = 0;
finishInd = i;
maxSubArrLen = i + 1;
}
else if (subArr.find(sumVal) == subArr.end())
subArr[sumVal] = i;
// 累積和が (sumVal - 1) となる位置が存在すれば更新
if (subArr.find(sumVal - 1) != subArr.end()) {
int currLen = (i - subArr[sumVal - 1]);
if (maxSubArrLen < currLen)
startInd = subArr[sumVal - 1] + 1;
finishInd = i;
maxSubArrLen = currLen;
}
}
return maxSubArrLen;
}
// 最大の部分行列面積を求める関数
int largestSubmatrix(int bin[SIZE][SIZE], int n){
int rows[n], maxSubMatArea = 0, currArea, longLen, startInd,
finishInd;
for (int left = 0; left < n; left++) {
memset(rows, 0, sizeof(rows));
for (int right = left; right < n; right++) {
// right列目の値を行ごとの累積に加算(0なら-1、1なら+1)
for (int i = 0; i < n; ++i){
if(bin[i][right] == 0)
rows[i] -= 1;
else
rows[i] += 1;
}
longLen = lenOfLongSubarr(rows, n, startInd, finishInd);
currArea = (finishInd - startInd + 1) * (right - left + 1);
if ((longLen != 0) && (maxSubMatArea < currArea)) {
maxSubMatArea = currArea;
}
}
}
return maxSubMatArea;
}
int main(){
int bin[SIZE][SIZE] = {
{ 1, 0, 0, 1 },
{ 0, 1, 1, 1 },
{ 1, 0, 0, 0 },
{ 0, 1, 0, 1 }
};
int n = 4;
cout << "1の数が0の数より1つ多い最大部分行列の面積は "
<< largestSubmatrix(bin, n);
return 0;
}
実行結果
1の数が0の数より1つ多い最大部分行列の面積は 9
まとめ
この問題のポイントは、2次元の部分行列の条件判定を「列のペアを固定 → 行方向の累積和を計算 → 区間和が1となる最長区間をハッシュマップで探索」という流れで1次元問題に落とし込むことです。これにより、素朴な全探索の O(n⁴) から O(n³) へと計算量を削減でき、より大きな行列にも対応できるようになります。
-
C++で正方形の面積を求めるプログラムの書き方
本記事では、正方形の一辺が与えられたときに、その一辺をもとに正方形の面積を計算して出力するC++プログラムを紹介します。 正方形とは 正方形とは、4つの辺と4つの角(すべて90度)を持つ2次元の平面図形であり、すべての辺の長さが等しいという特徴があります。言い換えれば、正方形とは「すべての辺の長さが等しい長方形」の一種であるとも言えます。 正方形のイメージは以下の通りです。 正方形の面積 = 一辺 × 一辺 入力例と出力例 入力:6 出力:36 一辺が6なので、出力は 6×6=36 となります。 入力:12 出力:144 アルゴリズム 処理の流れは以下のようになります。 関数 int m
-
C++で八面体の表面積を計算するプログラムの作成方法
八面体(オクタヘドロン)とは? 「Octahedron(八面体)」という言葉はギリシャ語に由来しています。「Octa」は「8」を、「hedron」は「面」を意味します。幾何学における八面体とは、8つの面を持つ3次元の正多面体(プラトンの立体)のことです。 他の立体図形と同様に、八面体にも以下のような特徴的な性質があります。 頂点の数:6個 辺の数:12本 面の数:8個(すべて正三角形) 以下は八面体の図です。 問題設定 一辺の長さが与えられたとき、その八面体の表面積を求めるプログラムを作成します。表面積とは、図形のすべての面が占める空間の総面積のことです。 八面体の表面積を計算するには