做过很多遍的二进制搜索,每次写就是对着代码敲一遍,然后下次写又不会


链接:http://poj.org/problem?id=3279

题意:n*m的矩阵,全是0和1构成的,当选择翻一个点的时候,周围的点会受到影响,问选择翻n*m矩阵中的哪些点,可以将所有点均翻过来?


为什么要用搜索?因为n和m很小,最大是15

为什么要用二进制搜索?因为是枚举状态,而且是每行每列的状态

怎么样枚举最好?枚举第一行,然后依次遍历其他行,枚举第一行的复杂度是2的n次方,然后遍历全矩阵式n*m,整个复杂度相乘没有问题


怎么搜索呢?

用一个整数表示1行的状态,比如255=11111111(2),那么循环就是从0到255表示256种情况,255的第0位至第7位均是1,所以呢,表示全部需要翻,然后从第二行到第n-1行依次推理得需要或者不需要翻,再用最后一行来检验方案是不是可行


const int maxn=20;
const int inf=0x3f3f3f3f;

int n,m;
int mp[maxn][maxn];
int mem[maxn][maxn];
int ans[maxn][maxn];

int mx[]={0,0,1,-1};
int my[]={1,-1,0,0};

bool ok(int x,int y){
	if (x<0||y<0||x>=m||y>=n) return false;
	return true;
}

bool calc(int x,int y){
	int ret=mp[x][y]+mem[x][y];
	for(int i=0;i<4;i++){
		int tx=x+mx[i];
		int ty=y+my[i];
		if (ok(tx,ty)) ret+=mem[tx][ty];
	}
	return ret%2; 
}

void nxt(int x,int y,int &tx,int &ty){
	tx=x;
	ty=y+1;
	if (ty==n){
		++tx;
		ty=0;
	}
}

int dfs(int x,int y,int num){
	if (x==m) return num;
	bool tem=calc(x-1,y);
	if (tem){
		mem[x][y]=true;
		++num;
	}
	int tx,ty;
	nxt(x,y,tx,ty);
	return dfs(tx,ty,num);
}

int search(int in){
	memset(mem,false,sizeof(mem));
	int ret=0;
	for(int i=0;i<n;i++)
		if ((in>>i)&1){
			mem[0][i]=true;
			++ret;
		}
	ret+=dfs(1,0,0);
	for(int i=0;i<n;i++)
		if (calc(m-1,i)) return inf;
	return ret;
}

int main(){
	//freopen("input.txt","r",stdin);
	while(scanf("%d%d",&m,&n)!=EOF){
		for(int i=0;i<m;i++)
			for(int j=0;j<n;j++) 
				scanf("%d",&mp[i][j]);
		int tem=inf;
		for(int i=0;i<(1<<n);i++){
			int rec=search(i);
			if (tem>rec){
				tem=rec;
				memcpy(ans,mem,sizeof(mem));
			}
		}
		if (tem==inf){
			puts("IMPOSSIBLE");
			continue;
		}
		for(int i=0;i<m;i++)
			for(int j=0;j<n;j++)
				printf("%d%c",ans[i][j],j==n-1?'\n':' ');
	}
	return 0;
}