C++でバイナリ行列を最大K回反転したときの最大スコアを求める方法
この記事では、0と1(ブール値)で構成される2次元配列 arr[] と整数 K が与えられたとき、バイナリ行列を最大 K 回反転した後の最大スコアを求めるプログラムを C++ で作成します。
問題の説明
2次元配列と K 回までの操作が与えられます。各操作では、任意の行または列を選び、その行(または列)に含まれるすべての要素を反転します(0 を 1 へ、1 を 0 へ)。操作の選択は、K 回の反転を行った後に、行列の各行が表す2進数の値が最大になるように行います。そして、各行で作られる数値の合計を答えとして返します。
具体例を使って問題を理解しましょう。
入力
arr[][] = {
{1, 0, 0},
{0, 1, 1},
{1, 0, 1}
}
K = 2
出力
19
解説
反転は2回行います。
1回目の反転: 2行目の要素を反転します。行列は次のようになります。
{{1, 0, 0},
{1, 0, 0},
{1, 0, 1}}
2回目の反転: 2列目の要素を反転します。行列は次のようになります。
{{1, 1, 0},
{1, 1, 0},
{1, 1, 1}}
このとき、各行が表す数値はそれぞれ 6、6、7 となります。
最大合計 = 6 + 6 + 7 = 19
解法アプローチ
この問題を解くうえでの重要なポイントは、左端の列(最上位ビット)を優先的に 1 にすることです。i 番目の列に置かれた 1 は 2(col−i−1) という重みでスコアに寄与するため、1つのセットビットとしては最も大きな価値を持ちます。したがって、合計を最大化するには、左端の列にできるだけ多くの 1(セットビット)を集める必要があります。そのうえで、各行の残りの要素についても判断を行っていきます。
行を反転すべきか列を反転すべきかは、次のように判断します。
- ある行の先頭要素が 0 の場合、その行を反転すれば先頭ビットが 1 になり、スコアを大きく伸ばせます。
- ある列に含まれる 0 の数が 1 の数より多い場合、その列を反転することでその列の 1 の数を増やせます。
本解法では、反転候補となる行の情報を管理するために map データ構造を使用します。
解法を実装したC++プログラム
#include <bits/stdc++.h>
using namespace std;
const int row = 3;
const int col = 3;
int MaxSumAfterFlip(int mat[row][col], int K) {
map<int, int> flipValues;
int updateVal, MaxSum = 0;
// 先頭列が 0 の行を反転候補として記録
for (int i = 0; i < row; ++i) {
if (mat[i][0] == 0) {
updateVal = 0;
for (int j = 1; j < col; ++j)
updateVal = updateVal + mat[i][j] * pow(2, col - j- 1);
flipValues[updateVal] = i;
}
}
// 候補の行を順に反転
map<int, int>::iterator it = flipValues.begin();
while (K > 0 && it != flipValues.end()) {
int updateIndex = it->second ;
for (int j = 0; j < col; ++j)
mat[updateIndex][j] = (mat[updateIndex][j] + 1) % 2;
it++;
K--;
}
// 列ごとに 0 と 1 の数を比較してスコアを計算
MaxSum = 0;
int zeros, ones = 0;
for (int j = 0; j < col; ++j) {
zeros = ones = 0;
for (int i = 0; i < row; ++i) {
mat[i][j] == 0 ? zeros++ : ones++;
}
if (K > 0 && zeros > ones) {
MaxSum += zeros * pow(2, (col - j - 1));
K--;
}
else
MaxSum += ones * pow(2, (col - j - 1));
}
return MaxSum;
}
int main() {
int mat[row][col] = {{1, 0, 0 },{0, 1, 1},{1, 0, 1}};
int K = 2;
cout<<"The Maximum score after flipping the matrix atmost K times is "<<MaxSumAfterFlip(mat, K);
return 0;
}
出力
The Maximum score after flipping the matrix atmost K times is 19
プログラムの動作解説
このプログラムは、次のステップで最大スコアを求めます。
- 反転候補の収集: 先頭列(最上位ビット)が 0 になっている行を検出し、その行の残りのビットで構成される値を計算して map に登録します。map はキー(値)の昇順に要素を保持するため、先頭から順に候補行を取り出して処理できます。
- 行の反転: 残り反転回数 K がある間、map から行インデックスを取り出し、その行の全要素を反転して先頭ビットを 1 にします。
- 列単位での評価: 各列について 0 と 1 の数を数えます。0 の方が多く、まだ反転回数が残っている場合は「その列を反転したもの」とみなして 0 の数 × ビットの重みを加算し、そうでなければ 1 の数 × 重みを加算します。
このアルゴリズムの計算量は O(row × col) であり、行列のサイズに対して線形時間で動作します。
まとめ
バイナリ行列の反転問題では、「最上位ビットにあたる左端の列を優先して 1 にする」という貪欲な方針が鍵となります。map を活用して反転候補の行を管理し、その後は列ごとに 0 と 1 の数を比較することで、最大 K 回の反転で得られる最大スコアを効率的に求めることができます。
-
【C++】バイナリ行列をすべて0に変換するための最小操作回数を求めるプログラム
問題概要0と1のみから構成されるバイナリ行列が与えられます。使用できる操作は「任意の1つのセルを選び、そのセル自身と上下左右の隣接するセル(存在する場合のみ)をすべて反転(0→1、1→0)する」というものです。この操作を繰り返して行列の全要素を0にするために必要な最小操作回数を求めてください。どのように操作してもすべて0にできない場合は -1 を返します。入力例{{0, 0}, {1, 0}}これは次のような2×2の行列です。0010出力3この場合、必要な操作回数は3回となります。解法のアプローチこの問題は、行列の状態をビットマスク(整数)として表現し、幅優先探索(BFS)で最短操作回数を求め
-
C++で二分木の最大垂直和を求める方法
はじめに二分木が与えられたとき、垂直順序走査における各垂直列のノード値の合計を計算し、その中から最大値を求めて出力するのが本記事の課題です。例として、以下のような二分木を考えてみましょう。この二分木を垂直順序走査すると、各列の合計は次のようになります。4 2 1 + 5 + 6 = 12 3 + 8 = 11 7 9各列の合計の中で最大となるのは 12 です。アルゴリズムの考え方アプローチはシンプルです。幅優先探索(BFS)を用いて垂直順序走査を行い、各ノードに水平距離を割り当てます。ルートの水平距離を 0 とし、左に移動するごとに -1、右に移動するごとに +1 とします。同じ水平距離を持つ