AOJ 0525 Osenbei 穷竭搜索 回溯

Description

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

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

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

地震の直後に,煎餅が次の図のような状態になったとする.黒い丸が表側が焼ける状態を,白い丸が裏側が焼ける状態を表している.
在这里插入图片描述
1行目を裏返すと次の図のような状態になる.
在这里插入图片描述
さらに, 1列目と5列目を裏返すと次の図のような状態になる.この状態では,出荷できる煎餅は9枚である.
在这里插入图片描述
ヒント
R の上限 10 は C の上限 10000 に比べて小さいことに注意せよ.

Input

入力は複数のデータセットからなる.各データセットは以下の形式で与えられる.
入力の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行に出力する.

Output

Simple Input

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

Simple Output

9
15

Problem mean

可以按一行或一列进行翻转,求0最多的情况。

Solution

由于只能按照一行或一列进行翻转,搜索的状态肯定也是按照行或者列进行的,并且由于1<=row<=10且1<=col<=10000,如果按照col搜索的话,至少为210000,所以我们按行搜索,210=1024。
对于每一行来说,有两种状态,1翻转,2不翻转。而如果确认了当前行的状态,就可以对列的状态进行确定了。对列来说,只要当前列0的个数比1多就不翻转,否则翻转。

Code

#include<iostream>
#include<cstring>
using namespace std;
bool a[15][10005];
bool b[15][10005];
int cntC[10005];
int bcntC[10005];
int r,c;
int ans=0;
void dfs(int row,bool flag){
	if(row==r+1){
		int sums=0;
		for(int i=1;i<=c;i++){
			sums+=cntC[i];
		}
		ans=max(ans,sums);
		return;
	}
	if(flag){
		memcpy(b,a,sizeof a);
		memcpy(bcntC,cntC,sizeof cntC);
		for(int i=1;i<=c;i++){
			if(!a[row][i])cntC[i]-=1;
			else cntC[i]+=1;
			if(cntC[i]<r-cntC[i]){
				cntC[i]=r-cntC[i];
				for(int j=1;j<=r;j++){
					if(j!=row)a[j][i]=!a[j][i];
				}
			}
			else{
				a[row][i]=!a[row][i];
			}
		}
		dfs(row+1,true);
		dfs(row+1,false);
		memcpy(a,b,sizeof b);//因为else会用到这些数组,所以回溯
		memcpy(cntC,bcntC,sizeof bcntC);
	}
	else {
		dfs(row+1,flag);
		dfs(row+1,true);
	}
}
int main(){
	while(cin >> r >> c,r){
		ans=0;	
		for(int i=1;i<=r;i++){
			for(int j=1;j<=c;j++){
				cin >> a[i][j];
			}
		}
		for(int j=1;j<=c;j++){
			int cnt=0;
			for(int i=1;i<=r;i++){
				if(!a[i][j])cnt++;
			}
			cntC[j]=cnt;
		}
		dfs(1,true);
		dfs(1,false);
		cout << ans << endl;
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值