AOJ : 0525 - おせんべい (Osenbei)
アルゴリズム
この問題のポイントは、列が10000もあるのに対し、行は10しかないところにあります。
ヒントまで書いてあります。
さて、この問題を解くためにはどうすればいいかというと、数の少ない「行」に注目します。
各行のせんべいを裏返すか裏返さないかを、再帰で決定するわけです。
これをやるだけなら O(2^n) で、この問題の場合、2^10 = 1024 程度にしかなりません。
そして、全行、裏返すか裏返さないかを決定したら、次は「列」に注目します。
さて、ここでまた全列再帰で計算すると、とてつもない計算量になってしまいます。
しかし、そんなことをしなくても、各列を裏返すか裏返さないかは、自然に決まります。
列の1の数を数えれば、裏返した後の1の数もわかります。
これによって、裏返すか裏返さないかで、どっちの方が生き残るせんべいが多いか、自然に決定できるわけです。
あとは、これを全列に対し行い、生き残ったせんべいの数を数え、最大値より大きかったら更新します。
プログラム
#include <stdio.h> #define REP(i,n,m) for(i=n;i<m;i++) #define rep(i,n) REP(i,0,n) int h,w; int ans; int flg[10],t[10][10000]; int count(void){ int i,j; int tmp, res = 0; rep(j,w){ //1の数を数える tmp = 0; rep(i,h) if(flg[i] && !t[i][j] || !flg[i] && t[i][j]) tmp++; if(tmp < h - tmp) //j列目を裏返した方がいい res += h - tmp; else //j列目を裏返さない方がいい res += tmp; } return res; } void solve(int idx){ int res; if(idx == h){ res = count(); if(ans < res) ans = res; return; } //idx行目を裏返さない flg[idx] = 0; solve(idx+1); //idx行目を裏返す flg[idx] = 1; solve(idx+1); } int main(void){ int i,j; while(scanf("%d%d",&h,&w) && (h||w)){ rep(i,h) rep(j,w) scanf("%d",&t[i][j]); ans = 0; solve(0); printf("%d\n",ans); } return 0; }