ACwing 116. 飞行员兄弟 dfs 二进制枚举

“飞行员兄弟”这个游戏,需要玩家顺利的打开一个拥有16个把手的冰箱。

已知每个把手可以处于以下两种状态之一:打开或关闭。

只有当所有把手都打开时,冰箱才会打开。

把手可以表示为一个4х4的矩阵,您可以改变任何一个位置[i,j]上把手的状态。

但是,这也会使得第i行和第j列上的所有把手的状态也随着改变。

请你求出打开冰箱所需的切换把手的次数最小值是多少。
输入格式

输入一共包含四行,每行包含四个把手的初始状态。

符号“+”表示把手处于闭合状态,而符号“-”表示把手处于打开状态。

至少一个手柄的初始状态是关闭的。
输出格式

第一行输出一个整数N,表示所需的最小切换把手次数。

接下来N行描述切换顺序,每行输入两个整数,代表被切换状态的把手的行号和列号,数字之间用空格隔开。

注意:如果存在多种打开冰箱的方式,则按照优先级整体从上到下,同行从左到右打开。
数据范围

1≤i,j≤4

输入样例:

-±-


-±-

输出样例:

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

注意点 :

  • 同一个开关按两次是没有意义的, 所以最多按16次
  • 我 们 用 二 进 制 位 代 表 开 或 关 的 状 态 比 如 000101 表 示 第 3 和 第 1 个 开 关 是 关 闭 的 我们用二进制位代表开或关的状态 比如 0 0 0 1 0 1 表示第3和第1个开关是关闭的 00010131
  • 我们直接把二进制亦或一个数字 X X X, 就可以得到操作同行同列的结果
    对于每个编号的 X X X要提前预处理出来,用 c h a n g e [   ] change[~] change[ ]数组记录这个数字 X X X
  • 我们把4*4的网格编号[0, 15]

  0        1        2        3 ~0~~~~~~1~~~~~~2~~~~~~3  0      1      2      3
  4        5        6        7 ~4~~~~~~5~~~~~~6~~~~~~7  4      5      6      7
  8        9       10       11 ~8~~~~~~9~~~~~10~~~~~11  8      9     10     11
12       13       14       15 12~~~~~13~~~~~14~~~~~15 12     13     14     15

  • 并用pi[]数组记录每个编号对应行列下标
    爆搜每个编号的位置按或不按的结果
    用status记录当前状态 操作完后status等于0就说明全开
  • 题目要求字典序最小 : 按行递增,行内按列递增即可
  • 取得第i行第k列的编号的公式 : ( i ∗ 4 + k ) ( i * 4 + k ) (i4+k)
  • 判断数字第i位是否为1方法 : i f ( n u m    i & 1 ) if( num \>\> i \& 1 ) if(numi&1)
  • y总牛逼!!!视频链接:https://www.acwing.com/video/93/
#define debug
#ifdef debug
#include <time.h>
#include </home/majiao/mb.h>
#endif

#include <iostream>
#include <algorithm>
#include <vector>
#include <string.h>
#include <map>
#include <set>
#include <stack>
#include <queue>
#include <math.h>

#define MAXN (8)
#define ll long long int
#define INF (0x7f7f7f7f)
#define fori(lef, rig) for(int i=lef; i<=rig; i++)
#define forj(lef, rig) for(int j=lef; j<=rig; j++)
#define fork(lef, rig) for(int k=lef; k<=rig; k++)
#define QAQ (0)

using namespace std;

#define show(x...) \
	do { \
	   cout << "\033[31;1m " << #x << " -> "; \
	   err(x); \
	} while (0)

void err() { cout << "\033[39;0m" << endl; }
template<typename T, typename... A>
void err(T a, A... x) { cout << a << ' '; err(x...); }

namespace FastIO{

	char print_f[105];
	void read() {}
	void print() { putchar('\n'); }

	template <typename T, typename... T2>
	   inline void read(T &x, T2 &... oth) {
		   x = 0;
		   char ch = getchar();
		   ll f = 1;
		   while (!isdigit(ch)) {
			   if (ch == '-') f *= -1; 
			   ch = getchar();
		   }
		   while (isdigit(ch)) {
			   x = x * 10 + ch - 48;
			   ch = getchar();
		   }
		   x *= f;
		   read(oth...);
	   }
	template <typename T, typename... T2>
	   inline void print(T x, T2... oth) {
		   ll p3=-1;
		   if(x<0) putchar('-'), x=-x;
		   do{
				print_f[++p3] = x%10 + 48;
		   } while(x/=10);
		   while(p3>=0) putchar(print_f[p3--]);
		   putchar(' ');
		   print(oth...);
	   }
} // namespace FastIO
using FastIO::print;
using FastIO::read;

int n, m, Q, K;
int change[MAXN<<2];

/** 

  同一个开关按两次是没有意义的, 所以最多按16次


  我们用二进制位代表开或关的状态
  比如 0 0 0 1 0 1 表示第3和第1个开关是关闭的


  我们直接把二进制亦或一个数字 X, 就可以得到操作同行同列的结果
  对于每个编号的X要提前预处理出来,用change[]数组记录这个数字X



  我们把4*4的网格编号[0, 15]
	0   1   2   3
	4   5   6   7
	8   9  10  11
   12  13  14  15
	
   并用pi[]数组记录每个编号对应行列下标
   爆搜每个编号的位置按或不按的结果
   用status记录当前状态 操作完后status等于0就说明全开


   题目要求字典序最小 : 按行递增,行内按列递增即可

   取得第i行第k列的编号的公式 : ( i * 4 + k )
   判断数字第i位是否为1方法   : if( num >> i & 1 )

 */

#define get(i, k) (i*4+k) //返回第i行第k列的编号
typedef pair<int, int> pii;
pii pi[MAXN<<4]; //记录每个编号对应的行列下标

signed main() {
#ifdef debug
	freopen("test", "r", stdin);
	clock_t stime = clock();
#endif
	int status = 0; //把status的所有二进制位都变成0就是一个解决方案
	for(int i=0; i<4; i++) {
		char buf[MAXN];
		scanf("%s ", buf);
		for(int j=0; j<4; j++) { //标记所有闭则的为1
			if(buf[j] == '+') 
				status |= (1 << get(i, j));
			pi[get(i, j)] = { i+1, j+1 }; //记录每个编号的行列
		}
	}

	for(int i=0; i<4; i++) //预处理所有位置应该亦或什么值X
		for(int j=0; j<4; j++) {
			for(int k=0; k<4; k++) change[get(i, j)] += (1<<get(i, k));
			for(int k=0; k<4; k++) change[get(i, j)] += (1<<get(k, j));
			change[get(i, j)] -= (1 << get(i, j));
		}

	int N = 1 << 16; //最多2^16种方案
	vector<pii> ans(32, pii()); //存储答案
	for(int i=1; i<N; i++) {
		int now = status, cnt = 0; //用now来表示当前状态
		vector<pii> vec;
		for(int j=0; j<16; j++) {
			if(i >> j & 1) //操作编号为j的位置
				now ^= change[j], vec.push_back(pi[j]), cnt ++;
		}
		if(!now && ans.size() > vec.size()) {
			ans = vec;
			//printf("%d\n", cnt);
			//for(auto v : vec)
			//	printf("%d %d\n", v.first, v.second);
		}
	}
	if(!ans.size()) { printf("-1\n"); return 0; }
	printf("%d\n", (int)ans.size());
	for(auto v : ans)
		printf("%d %d\n", v.first, v.second);




#ifdef debug
   clock_t etime = clock();
   printf("rum time: %lf 秒\n",(double) (etime-stime)/CLOCKS_PER_SEC);
#endif 
   return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值