POJ 2965 - The Pilots Brothers' refrigerator

The Pilots Brothers' refrigerator
Time Limit: 1000MS Memory Limit: 65536K
Total Submissions: 24341 Accepted: 9395 Special Judge

Description

The game “The Pilots Brothers: following the stripy elephant” has a quest where a player needs to open a refrigerator.

There are 16 handles on the refrigerator door. Every handle can be in one of two states: open or closed. The refrigerator is open only when all handles are open. The handles are represented as a matrix 4х4. You can change the state of a handle in any location [i, j] (1 ≤ i, j ≤ 4). However, this also changes states of all handles in row i and all handles in column j.

The task is to determine the minimum number of handle switching necessary to open the refrigerator.

Input

The input contains four lines. Each of the four lines contains four characters describing the initial state of appropriate handles. A symbol “+” means that the handle is in closed state, whereas the symbol “−” means “open”. At least one of the handles is initially closed.

Output

The first line of the input contains N – the minimum number of switching. The rest N lines describe switching sequence. Each of the lines contains a row number and a column number of the matrix separated by one or more spaces. If there are several solutions, you may give any one of them.

Sample Input

-+--
----
----
-+--

Sample Output

6
1 1
1 3
1 4
4 1
4 3
4 4

Source

Northeastern Europe 2004, Western Subregion

传送门: 点击打开链接

题意:一个4*4大小的门上一共有16个把手,每个把手只有两种状态(open和close)。每次操作选择一个把手改变其状态,并且该把手所在行和列上的 其余所有把手都会改变状态,求最小的操作次数及其具体操作,使得所有把手的状态全部为open(‘-’)。

首先需要分析出几个信息:1.同一个把手操作偶数次状态不会变化,相当于没有操作。2.操作奇数次相当于操作1次。

这里给出4种方法,速度从快到慢。

第一种方法:最容易想到的方法就是暴力枚举每种操作,记录满足条件的最小操作次数和操作。这里用了一个小技巧,就是将每种操作状态压缩成一个二进制数,使用位运算操作,1代表open,0代表close。这种方法的缺点在于会有很多无效状态的枚举,因为你并不知道这个操作次数是不是最小的,一定要遍历完所有的状态,但是相对于第二种方法来说好写。
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<string.h>
#include<string>
#include<ctype.h>
#include<queue>
#include<map>
#include<set>
#include<stack>
#include<vector>
#include<algorithm>
#include<iostream>
#include<functional>
const int INF=0x3f3f3f3f;
const double PI=acos(-1.0);
const double EPS=1e-8;
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

char str[5];
int state=0;

int main(){
	for(int i=0;i<4;++i){
		scanf("%s",str);
		for(int j=0;j<4;++j){
			if(str[j]=='-') state|=1<<(i*4+j);
		}
	}
	int ans=INF;
	int MinState;
	for(int i=0;i<(1<<16);++i){
		int t=state;
		int num=0;
		for(int j=0;j<16;++j){
			if(i&(1<<j)){
				num+=1;
				if(num==ans) break;
				int raw=j/4,col=j%4;
				for(int k=0;k<4;++k){
					t^=1<<(raw*4+k);
					t^=1<<(k*4+col);
				}
				t^=1<<j;
			}
		}
		if(t==(1<<16)-1){
			ans=min(ans,num);
			MinState=i;
			if(ans==1) break;
		}
	}
	printf("%d\n",ans);
	for(int i=0;i<16;++i){
		if(MinState&(1<<i)) printf("%d %d\n",i/4+1,i%4+1);
	}
	return 0;
}


第二种方法:从小到大枚举操作次数,用DFS搜索状态,一但搜索到了就是最小的操作次数,本质上是迭代加深搜索。
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<string.h>
#include<string>
#include<ctype.h>
#include<queue>
#include<map>
#include<set>
#include<stack>
#include<vector>
#include<algorithm>
#include<iostream>
#include<functional>
const int INF=0x3f3f3f3f;
const double PI=acos(-1.0);
const double EPS=1e-8;
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

int chessboard[4][5];
int path[16][2];

void turn(int row,int col,int now){
	path[now][0]=row+1;
	path[now][1]=col+1;
	chessboard[row][col]^=1;
	for(int i=0;i<4;++i){
		chessboard[row][i]^=1;
		chessboard[i][col]^=1;
	}
}
bool dfs(int row,int col,int n,int now){
	if(now==n){
		for(int i=0;i<4;++i){
			for(int j=0;j<4;++j){
				if(chessboard[i][j]==0) return false;
			}
		}
		return true;
	}
	turn(row,col,now);
	for(int i=row*4+col+1;i<=16-(n-now-1);++i){
		if(dfs(i/4,i%4,n,now+1)) return true;
	}
	turn(row,col,now);
	return false;
}

int main(){
	char str[5];
	for(int i=0;i<4;++i){
		scanf("%s",str);
		for(int j=0;j<4;++j){
			if(str[j]=='-') chessboard[i][j]=1;
		}
	}
	int flag=0;
	for(int i=0;i<=16;++i){
		for(int j=0;j<=16-i;++j){
			if(dfs(j/4,j%4,i,0)){
				printf("%d\n",i);
				for(int j=0;j<i;++j) printf("%d %d\n",path[j][0],path[j][1]);
				flag=1;
				break;
			}
		}
		if(flag) break;
	}
	return 0;
}


第三种方法:一种很巧妙的方法,不需要暴力枚举,直接可以求出答案。首先需要想到,要改变一个把手的状态而其余15个把手状态不变,只需将该把手所在行列上的所有把手全部操作一次。那么直接预处理出把所有‘+’变成‘-’的每个把手的操作次数,最后判断如果是偶数次就不操作,奇数次则操作一次。
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<string.h>
#include<string>
#include<ctype.h>
#include<queue>
#include<map>
#include<set>
#include<stack>
#include<vector>
#include<algorithm>
#include<iostream>
#include<functional>
const int INF=0x3f3f3f3f;
const double PI=acos(-1.0);
const double EPS=1e-8;
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

int open[4][4];
int path[16][2];

int main(){
	char str[5];
	for(int i=0;i<4;++i){
		scanf("%s",str);
		for(int j=0;j<4;++j){
			if(str[j]=='+'){
				open[i][j]-=1;
				for(int k=0;k<4;++k){
					open[i][k]+=1;
					open[k][j]+=1;
				}
			}
		}
	}
	int num=0;
	for(int i=0;i<4;++i){
		for(int j=0;j<4;++j){
			if(open[i][j]%2){
				path[num][0]=i+1;
				path[num++][1]=j+1;
			}
		}
	}
	printf("%d\n",num);
	for(int i=0;i<num;++i){
		printf("%d %d\n",path[i][0],path[i][1]);
	}
	return 0;
}

第四种方法:高斯消元,推荐使用这种方法,也是解决开关问题普遍使用的一种方法。前两种方法在n过大时容易超时,而第三种方法不适用于所有开关问题,如POJ 1753。
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<string.h>
#include<string>
#include<ctype.h>
#include<queue>
#include<map>
#include<set>
#include<stack>
#include<vector>
#include<algorithm>
#include<iostream>
#include<functional>
const int INF=0x3f3f3f3f;
const double PI=acos(-1.0);
const double EPS=1e-8;
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

int A[16][17];

int Gauss(){
	for(int i=0;i<16;++i){
		int j=i;
		for(;j<16;++j){
			if(A[j][i]) break;
		}
		for(int k=0;k<17;++k) swap(A[i][k],A[j][k]);
		for(int j=0;j<16;++j){
			if(j!=i && A[j][i]){
				for(int k=0;k<17;++k) A[j][k]^=A[i][k];
			}
		}
	}
	int sum=0;
	for(int i=0;i<16;++i){
		if(A[i][16]) ++sum;
	}
	return sum;
}
int main(){
	for(int i=0;i<4;++i){
		for(int j=0;j<4;++j){
			A[i*4+j][i*4+j]^=1;
			for(int k=0;k<4;++k){
				A[i*4+j][i*4+k]^=1;
				A[i*4+j][k*4+j]^=1;
			}
		}
	}
	char str[5];
	for(int i=0;i<4;++i){
		scanf("%s",str);
		for(int j=0;j<4;++j){
			if(str[j]=='+') A[i*4+j][16]=1;
		}
	}
	printf("%d\n",Gauss());
	for(int i=0;i<16;++i){
		if(A[i][16]) printf("%d %d\n",i/4+1,i%4+1);
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值