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;
}

题目链接:https://www.acwing.com/problem/content/4948/ 题目描述: 给定一棵有 $n$ 个结点的树,结点从 $1$ 到 $n$ 编号,每个结点都有一个权值 $w_i$,现在有 $m$ 次操作,每次操作是将树中编号为 $x$ 的结点的权值加上 $y$,然后询问一些节点是否为叶子节点,如果是输出 $1$,否则输出 $0$。 输入格式: 第一行包含两个整数 $n$ 和 $m$。 第二行包含 $n$ 个整数,其中第 $i$ 个整数表示结点 $i$ 的初始权值 $w_i$。 接下来 $n-1$ 行,每行包含两个整数 $a$ 和 $b$,表示点 $a$ 和点 $b$ 之间有一条无向边。 接下来 $m$ 行,每行描述一次操作,格式为三个整数 $t,x,y$。其中 $t$ 表示操作类型,$t=1$ 时表示将编号为 $x$ 的结点的权值加上 $y$,$t=2$ 时表示询问编号为 $x$ 的结点是否为叶子节点。 输出格式: 对于每个操作 $t=2$,输出一个结果,表示询问的结点是否为叶子节点。 数据范围: $1≤n,m≤10^5$, $1≤w_i,y≤10^9$ 样例: 输入: 5 5 1 2 3 4 5 1 2 1 3 3 4 3 5 2 3 0 1 3 100 2 3 0 1 1 100 2 3 0 输出: 1 0 0 算法1: 暴力dfs,每次都重新遍历整棵树,时间复杂度 $O(nm)$ 时间复杂度: 最坏情况下,每次操作都要遍历整棵树,时间复杂度 $O(nm)$,无法通过此题。 算法2: 用一个 vector<int> sons[n+5] 来存储每个点的所有子节点,这样可以用 $O(n)$ 预处理出每个点的度数 $deg_i$,如果 $deg_i=0$,则 $i$ 是叶子节点,否则不是。 对于每个操作,只需要更新叶子节点关系的变化就可以了。如果某个节点的度数从 $1$ 变成 $0$,则该节点变成了叶子节点;如果某个节点的度数从 $0$ 变成 $1$,则该节点不再是叶子节点。 时间复杂度: 每次操作的时间复杂度是 $O(1)$,总时间复杂度 $O(m)$,可以通过此题。 C++ 代码: (算法2)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值