Problem:
おせんべい

問題

IOI製菓では，創業以来の伝統の製法で煎餅（せんべい）を焼いている．この伝統の製法は，炭火で一定時間表側を焼き，表側が焼けると裏返して，炭火で一定時間裏側を焼くというものである．この伝統を守りつつ，煎餅を機械で焼いている．この機械は縦 R (1 ≤ R ≤ 10) 行, 横 C (1 ≤ C ≤ 10000) 列の長方形状に煎餅を並べて焼く．通常は自動運転で，表側が焼けたら一斉に煎餅を裏返し裏側を焼く．

ある日，煎餅を焼いていると，煎餅を裏返す直前に地震が起こり何枚かの煎餅が裏返ってしまった．幸いなことに炭火の状態は適切なままであったが，これ以上表側を焼くと創業以来の伝統で定められている焼き時間を超えてしまい，煎餅の表側が焼けすぎて商品として出荷できなくなる．そこで，急いで機械をマニュアル操作に変更し，まだ裏返っていない煎餅だけを裏返そうとした．この機械は，横の行を何行か同時に裏返したり縦の列を何列か同時に裏返したりすることはできるが，残念なことに，煎餅を１枚ごと裏返すことはできない．

裏返すのに時間がかかると，地震で裏返らなかった煎餅の表側が焼けすぎて商品として出荷できなくなるので，横の何行かを同時に１回裏返し，引き続き，縦の何列かを同時に１回裏返して，表側を焼きすぎずに両面を焼くことのできる煎餅，つまり，「出荷できる煎餅」の枚数をなるべく多くすることにした．横の行を１行も裏返さない，あるいは，縦の列を１列も裏返さない場合も考えることにする．出荷できる煎餅の枚数の最大値を出力するプログラムを書きなさい．

地震の直後に，煎餅が次の図のような状態になったとする．黒い丸が表側が焼ける状態を，白い丸が裏側が焼ける状態を表している．

1行目を裏返すと次の図のような状態になる．

さらに， 1列目と5列目を裏返すと次の図のような状態になる．この状態では，出荷できる煎餅は9枚である．

ヒント

R の上限 10 は C の上限 10000 に比べて小さいことに注意せよ．

入力

入力は複数のデータセットからなる．各データセットは以下の形式で与えられる．

入力の1行目には2つの整数 R, C (1 ≤ R ≤ 10, 1 ≤ C ≤ 10 000) が空白を区切りとして書かれている．続く R 行は地震直後の煎餅の状態を表す． (i+1) 行目 (1 ≤ i ≤ R) には， C 個の整数 ai,1, ai,2, ……, ai,C が空白を区切りとして書かれており， ai,j は i 行 j 列 の煎餅の状態を表している. ai,j が 1 なら表側が焼けることを， 0 なら裏側が焼けることを表す．

C, R がともに 0 のとき入力の終了を示す.　データセットの数は 5 を超えない．

出力

データセットごとに，出荷できる煎餅の最大枚数を1行に出力する．

入出力例

入力例

2 5
0 1 0 1 0
1 0 0 0 1
3 6
1 0 0 0 1 0
1 1 1 0 1 0
1 0 1 1 0 1
0 0

出力例

9
15

上記問題文と自動審判に使われるデータは、情報オリンピック日本委員会が作成し公開している問題文と採点用テストデータです。

Solution in C (judge verdict: Accepted)

#include <stdio.h>

#define R_MAX 10    // 最大行サイズ
#define C_MAX 10000 // 最大列サイズ

#define MAX(a,b) ((a)>(b)?(a):(b))

int main( void );
void solve( int input_row, int input_col, int input_ary[R_MAX][C_MAX]);

// 型定義
typedef unsigned char  uint8;
typedef unsigned short uint16;
typedef unsigned long  uint32;
typedef signed   char  sint8;
typedef signed   short sint16;
typedef signed   long  sint32;

int main( void )
{
    int input_row;               // [入力]行数(scanf用なのでint型)
    int input_col;               // [入力]列数(scanf用なのでint型)
    uint16 u2col;                // 列走査用一時変数
    uint8  u1row;                // 行走査用一時変数
    int input_ary[R_MAX][C_MAX]; // [入力]input_ary[行][列] : 煎餅の裏表(0:裏,1:表)が格納された2重配列(scanf用なのでint型) 
    //入力
    while(1)
    {
        scanf("%d %d",&input_row,&input_col);

        if( (input_row == 0) && (input_col == 0 ) )
        {
            break;
        }
        for( u1row = (uint8)0U; u1row < (uint8)input_row; u1row++ ){
            for( u2col = (uint16)0U; u2col < (uint16)input_col; u2col++ ){
                scanf("%d",&(input_ary[u1row][u2col]));
            }
        }
        
        // 問題解答
        solve( input_row, input_col, input_ary );
    }

    return 0;
}

// 問題解答用関数
void solve( int input_row, int input_col, int input_ary[R_MAX][C_MAX]){
    uint32 u4sellable_max;       // [出力]出荷できる最大値
    uint32 u4sellable_cnt;       // ある列の出荷できる総数
    uint16 u2row_pattern;        // 行反転パターン番号
    uint16 u2row_pattern_num;    // 行反転パターンの数
    uint16 u2col;                // 列走査用一時変数
    uint8  u1row;                // 行走査用一時変数
    uint8  u1row_reverse[R_MAX]; // 各行の反転要否(0：反転しない、1：反転する)
    uint8  u1front_cnt;          // 表の数
    uint8  u1back_cnt;           // 裏の数

    // 行ごとに反転させるかどうかを全パターン試す。
    // 2^row(==1<<row)パターンあり、
    // 2進数表現x桁目とx行を対応させ
    //  0：反転しない、1：反転する と考える。
    u4sellable_max = 0UL;
    u2row_pattern_num = (uint16)(1U << (uint8)input_row); // input_rowは最大10なのでuint16のサイズを超えない
    for( u2row_pattern = (uint16)0U; u2row_pattern < u2row_pattern_num; u2row_pattern++ ){
        // 各行の反転要否を扱いやすくするため配列に格納しておく
        for( u1row = (uint8)0U; u1row < (uint8)input_row; u1row++ ){
            u1row_reverse[u1row] = (uint8)( (u2row_pattern>>u1row) & (uint16)0x0001U );
        }

        // 列ごとに出荷できる数を求め、合計する
        u4sellable_cnt = 0UL;
        for( u2col = (uint16)0U; u2col < (uint16)input_col; u2col++ ){
            // 裏の数を数える
            u1back_cnt = (uint8)0U;
            for( u1row = (uint8)0U; u1row < (uint8)input_row; u1row++ ){
                // [0(裏),0(反転しない)] または [1(表),1(反転する)] のとき裏
                if( ((uint8)input_ary[u1row][u2col] ^ u1row_reverse[u1row]) == (uint8)0U ){
                    u1back_cnt++;
                }
            }

            // 表の数の方が多ければ裏返せばよいので、表と裏の大きいほうの数が出荷できる
            u1front_cnt     = (uint8)input_row - u1back_cnt;
            u4sellable_cnt += MAX( u1front_cnt, u1back_cnt );
        }

        // 最大値を更新する
        if( u4sellable_max < u4sellable_cnt ){
            u4sellable_max = u4sellable_cnt;
        }
    }

    //出力
    printf("%d\n",u4sellable_max);
}