【基础算法】枚举(普通枚举、二进制枚举)

文章目录

  • 一、普通枚举
    • 1. 铺地毯
      • (1) 解题思路
      • (2) 代码实现
    • 2. 回文日期
      • (1) 解题思路
        • 思路一:暴力枚举
        • 思路二:枚举年份
        • 思路三:枚举月日
      • (2) 代码实现
    • 3. 扫雷
      • (2) 解题思路
      • (2) 代码实现
  • 二、二进制枚举
    • 1. 子集
      • (1) 解题思路
      • (2) 代码实现
    • 2. 费解的开关
      • (1) 解题思路
      • (2) 代码实现
    • 3. even parity
      • (1) 解题思路
      • (2) 代码实现

一、普通枚举

顾名思义,就是把所有情况全部罗列出来,然后找到符合题目要求的那一个,因此它是一种纯暴力的算法。

一般情况下,枚举策略都是会超时的。此时要先根据题目的数据范围来判断暴力枚举是否可以通过。 如果不行的话,就要考虑用其他各种算法来进行优化(比如⼆分,双指针,前缀和与差分等)。

使用枚举策略时,重点思考枚举的对象(枚举什么),枚举的顺序(正序还是逆序),以及枚举的方式(普通枚举?⼆进制枚举?递归枚举?)

1. 铺地毯

【题目链接】

[P1003 NOIP 2011 提高组] 铺地毯 - 洛谷

【题目描述】

为了准备一个独特的颁奖典礼,组织者在会场的一片矩形区域(可看做是平面直角坐标系的第一象限)铺上一些矩形地毯。一共有 n n n 张地毯,编号从 1 1 1 n n n。现在将这些地毯按照编号从小到大的顺序平行于坐标轴先后铺设,后铺的地毯覆盖在前面已经铺好的地毯之上。

地毯铺设完成后,组织者想知道覆盖地面某个点的最上面的那张地毯的编号。注意:在矩形地毯边界和四个顶点上的点也算被地毯覆盖。

【输入格式】

输入共 n + 2 n + 2 n+2 行。

第一行,一个整数 n n n,表示总共有 n n n 张地毯。

接下来的 n n n 行中,第 i + 1 i+1 i+1 行表示编号 i i i 的地毯的信息,包含四个整数 a , b , g , k a ,b ,g ,k a,b,g,k,每两个整数之间用一个空格隔开,分别表示铺设地毯的左下角的坐标 ( a , b ) (a, b) (a,b) 以及地毯在 x x x 轴和 y y y 轴方向的长度。

n + 2 n + 2 n+2 行包含两个整数 x x x y y y,表示所求的地面的点的坐标 ( x , y ) (x, y) (x,y)

【输出格式】

输出共 1 1 1 行,一个整数,表示所求的地毯的编号;若此处没有被地毯覆盖则输出 -1

【示例一】

输入

3
1 0 2 3
0 2 3 3
2 1 3 3
2 2

输出

3

【示例二】

输入

3
1 0 2 3
0 2 3 3
2 1 3 3
4 5

输出

-1

【说明/提示】

【样例解释 1】

如下图, 1 1 1 号地毯用实线表示, 2 2 2 号地毯用虚线表示, 3 3 3 号用双实线表示,覆盖点 ( 2 , 2 ) (2,2) (2,2) 的最上面一张地毯是 3 3 3 号地毯。

【数据范围】

对于 30 % 30\% 30% 的数据,有 n ≤ 2 n \le 2 n2
对于 50 % 50\% 50% 的数据, 0 ≤ a , b , g , k ≤ 100 0 \le a, b, g, k \le 100 0a,b,g,k100
对于 100 % 100\% 100% 的数据,有 0 ≤ n ≤ 10 4 0 \le n \le 10^4 0n104, 0 ≤ a , b , g , k ≤ 10 5 0 \le a, b, g, k \le {10}^5 0a,b,g,k105

noip2011 提高组 day1 第 1 1 1 题。


(1) 解题思路

枚举每一个地毯,判断该该点是否被这个地毯覆盖。注意枚举的时候最好逆序枚举。因为我们需要找的是最上面的地毯,如果顺序枚举的话那么必须要枚举完所有情况才能找到最上面的地毯,而逆序枚举的话所找到第一个覆盖的地毯一定是最上面的地毯

在判断一个点是否被一个地毯覆盖的时候只需要用到简单的数学知识即可。假设地毯左下角点的坐标为 (a, b),由题意,这张地毯的右上角点的坐标为 (a + g, b + k)。那么如果一个点 (x, y) 被覆盖,一定有 a <= x <= a + gb <= y <= b + k


(2) 代码实现

#include<iostream>using namespace std;const int N = 1e4 + 10;
int a[N], b[N], g[N], k[N];
int n, x, y;// 寻找最上面的地毯并返回其下标,没找到返回-1
int find()
{for(int i = n; i >= 1; --i)  // 逆序枚举每一个地毯{if(x >= a[i] && x <= a[i] + g[i]&& y >= b[i] && y <= b[i] + k[i]){return i;}}return -1;
}int main()
{cin >> n;for(int i = 1; i <= n; ++i)cin >> a[i] >> b[i] >> g[i] >> k[i];cin >> x >> y;cout << find();return 0;
}

2. 回文日期

【题目链接】

[P2010 NOIP 2016 普及组] 回文日期 - 洛谷

【题目描述】

在日常生活中,通过年、月、日这三个要素可以表示出一个唯一确定的日期。

牛牛习惯用 8 8 8 位数字表示一个日期,其中,前 4 4 4 位代表年份,接下来 2 2 2 位代表月份,最后 2 2 2 位代表日期。显然:一个日期只有一种表示方法,而两个不同的日期的表 示方法不会相同。

牛牛认为,一个日期是回文的,当且仅当表示这个日期的 8 8 8 位数字是回文的。现在,牛牛想知道:在他指定的两个日期之间(包含这两个日期本身),有多少个真实存在的日期是回文的。

一个 8 8 8 位数字是回文的,当且仅当对于所有的 i i i 1 ≤ i ≤ 8 1 \le i \le 8 1i8)从左向右数的第 i i i 个数字和第 9 − i 9-i 9i 个数字(即从右向左数的第 i i i 个数字)是相同的。

例如:

  • 对于 2016 年 11 月 19 日,用 8 8 8 位数字 20161119 20161119 20161119 表示,它不是回文的。
  • 对于 2010 年 1 月 2 日,用 8 8 8 位数字 20100102 20100102 20100102 表示,它是回文的。
  • 对于 2010 年 10 月 2 日,用 8 8 8 位数字 20101002 20101002 20101002 表示,它不是回文的。

每一年中都有 12 12 12 个月份:

其中, 1 , 3 , 5 , 7 , 8 , 10 , 12 1, 3, 5, 7, 8, 10, 12 1,3,5,7,8,10,12 月每个月有 31 31 31 天; 4 , 6 , 9 , 11 4, 6, 9, 11 4,6,9,11 月每个月有 30 30 30 天;而对于 2 2 2 月,闰年时有 29 29 29 天,平年时有 28 28 28 天。

一个年份是闰年当且仅当它满足下列两种情况其中的一种:

  1. 这个年份是 4 4 4 的整数倍,但不是 100 100 100 的整数倍;
  2. 这个年份是 400 400 400 的整数倍。

例如:

  • 以下几个年份都是闰年: 2000 , 2012 , 2016 2000, 2012, 2016 2000,2012,2016
  • 以下几个年份是平年: 1900 , 2011 , 2014 1900, 2011, 2014 1900,2011,2014

【输入格式】

两行,每行包括一个 8 8 8 位数字。

第一行表示牛牛指定的起始日期。

第二行表示牛牛指定的终止日期。

保证 d a t e 1 \mathit{date}_1 date1 d a t e 2 \mathit{date}_2 date2 都是真实存在的日期,且年份部分一定为 4 4 4 位数字,且首位数字不为 0 0 0

保证 d a t e 1 \mathit{date}_1 date1 一定不晚于 d a t e 2 \mathit{date}_2 date2

【输出格式】

一个整数,表示在 d a t e 1 \mathit{date}_1 date1 d a t e 2 \mathit{date}_2 date2 之间,有多少个日期是回文的。

【示例一】

输入

20110101
20111231

输出

1

【示例二】

输入

20000101
20101231

输出

2

【说明/提示】

【样例说明】

对于样例 1,符合条件的日期是 20111102 20111102 20111102

对于样例 2,符合条件的日期是 20011002 20011002 20011002 20100102 20100102 20100102

【子任务】

对于 60 % 60 \% 60% 的数据,满足 d a t e 1 = d a t e 2 \mathit{date}_1 = \mathit{date}_2 date1=date2


(1) 解题思路

  • 思路一:暴力枚举

最容易想到的,就是设置一个变量 cnt 记录回文日期的个数,枚举两个日期中间的所有数字,判断是否是回文日期,如果是,那么判断是否是一个合法的日期,如果合法那么 cnt++

  • 思路二:枚举年份

在暴力枚举的过程中,我们实际上枚举了很多没有用的数字。通过观察可以发现,如果一个日期数字的前四位(也就是年份)确定了的话,那么它的后四位也就随之确定了。比如 2010 2010 2010,想要构成回文日期的话那么它的后四位必定是 0102 0102 0102。因此我们仅需枚举前四位(年份)再加判断是否合法即可。

  • 思路三:枚举月日

枚举年份的情况有可能还是太多了,我们可以只枚举月日,即枚举后四位,那么年份也就确定了。而在这种情况下我们仅需最多枚举 365 / 366 种情况,将日月组成的数字倒过来拼成年份的数字,最终将组成的回文日期与题目给的日期作比较判断是否在题目给定的范围之内即可,大大减少了时间复杂度。


(2) 代码实现

#include<iostream>using namespace std;int date1, date2;
int day[] = {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};int main()
{cin >> date1 >> date2;int cnt = 0;for(int i = 1; i <= 12; i++){for(int j = 1; j <= day[i]; j++){int y = (j % 10) * 1000 + (j / 10) * 100 + (i % 10) * 10 + (i / 10);  // 倒过来拼成年份int num = y * 10000 + i * 100 + j;  // 拼成回文日期if(date1 <= num && num <= date2) cnt++;  // 判断是否在题目给定的范围内}}cout << cnt;return 0;
}

3. 扫雷

【题目链接】

[P2327 SCOI2005] 扫雷 - 洛谷

【题目描述】

相信大家都玩过扫雷的游戏。那是在一个 n × m n\times m n×m 的矩阵里面有一些雷,要你根据一些信息找出雷来。万圣节到了,“余”人国流行起了一种简单的扫雷游戏,这个游戏规则和扫雷一样,如果某个格子没有雷,那么它里面的数字表示和它 8 8 8 连通的格子里面雷的数目。现在棋盘是 n × 2 n\times 2 n×2 的,第一列里面某些格子是雷,而第二列没有雷,如下图:

由于第一列的雷可能有多种方案满足第二列的数的限制,你的任务即根据第二列的信息确定第一列雷有多少种摆放方案。

【输入格式】

第一行为 N N N,第二行有 N N N 个数,依次为第二列的格子中的数。( 1 ≤ N ≤ 10000 1\le N\le10000 1N10000

【输出格式】

一个数,即第一列中雷的摆放方案数。

【示例一】

输入

2
1  1

输出

2

(2) 解题思路

通过观察可以发现,当我们确定第一列第一个位置是否有雷时,那么第一列雷的排列情况也就全部确定了。所以,我们仅需枚举出第一列第一个位置放或不放地雷两种情况,然后检查这两种情况下的地雷分布是否合法。

具体来说,我们可以用两个数组 a[N]b[N] 来保存第一列地雷分布情况(0 表示无地雷,1 表示有地雷)和第二列的数字。那么第一列的第 i 个位置是否有地雷就可以这样计算:a[i] = b[i - 1] - a[i - 2] - a[i - 1],如果 a[i] 小于 0 或大于 1,说明这种情况不合法,即不存在这种情况。

还需要注意两个细节问题:

  1. 两数组的下标可以从 1 开始计数,因为当第一个位置确定之后我们是从第二个位置开始枚举的,如果从下标为 0 开始计数则对应 a[1],计算时的 a[i - 2] 会发生越界的情况。所以从下标为 1 开始计数可以有效避免这种情况。

  2. 如果一共有 n 个数,那么循环遍历要遍历到下标 n + 1 处,当 a[n + 1] 不为 0 的时候,同样也是不合法的情况。比如当第二列是数字 1, 3 时,实际上是不合法的,这个时候我们可以通过计算 a[3] 的位置是否为 0 来判断。


(2) 代码实现

#include<iostream>using namespace std;const int N = 1e4 + 10;
int a[N], b[N];
int n;int check1()
{a[1] = 0;for(int i = 2; i <= n + 1; i++){a[i] = b[i - 1] - a[i - 1] - a[i - 2];if(a[i] < 0 || a[i] > 1) return 0;  // 不合法的情况}if(a[n + 1] == 0) return 1;else return 0;  // n + 1 位置不为 0 也是不合法的
}int check2()
{a[1] = 1;for(int i = 2; i <= n + 1; i++){a[i] = b[i - 1] - a[i - 1] - a[i - 2];if(a[i] < 0 || a[i] > 1) return 0;}if(a[n + 1] == 0) return 1;else return 0;
}int main()
{cin >> n;for(int i = 1; i <= n; i++) cin >> b[i];int ans = 0;ans += check1();  // a[1] 不放地雷ans += check2();  // a[1] 放地雷cout << ans;return 0;
}

二、二进制枚举

二进制枚举:用一个数二进制表示中的 0/1 表示两种状态,从而达到枚举各种情况。

注:利用二进制枚举时,会用到⼀些位运算的知识。

多说无益,直接实战!


1. 子集

【题目链接】

78. 子集 - 力扣(LeetCode)

【题目描述】

给你⼀个整数数组 nums ,数组中的元素互不相同。返回该数组所有可能的⼦集(幂集)。 解集不能包含重复的⼦集。你可以按任意顺序返回解集。

【示例一】

输⼊

nums = [1,2,3] 

输出

[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

(1) 解题思路

可以发现,数组 nums 中的数字都有选或不选两种情况,那么我们可以用 0 和 1 分别来表示不选和选这两种状态。通过二进制的方式来枚举出所有的子集。并且含有 n 个数的集合,其子集一共有 2 ^ n 个,即 1 << n 个。

于是,当我们有了一个二进制数比如 101 时,表示第 1,3 位要放到子集中,形成 [1, 3]。那么如何用代码实现对应位的取舍呢?我们可以用一个变量 i 遍历二进制的每一位,如果第 i 位为 1,那么表示取,那么对应的 nums[i] 就存放在一个子集中。

判断第 i 位是否为 1:(对应的二进制数 >> i) & 1

原理就是把这个二进制数的第 i 位移到最低位,然后和 1 进行按位与操作,如果这个位是 1,那么结果就是 1,否则为 0。

十进制二进制子集判断取舍(用 i 遍历二进制的每一位)
0000[]所有位为 0,都不取
1001[1]i = 0: 001 >> 0 &1 = 1 (取)
2010[2]i = 1: 010 >> 1 &1 = 1 (取)
3011[1,2]i = 0, 1 为真
4100[3]i = 2: 100 >> 2 &1 = 1 (取)
5101[1,3]i = 0, 2 为真
6110[2,3]i = 1, 2 为真
7111[1,2,3]所有位为真,都取

(2) 代码实现

class Solution 
{
public:vector<vector<int>> subsets(vector<int>& nums) {vector<vector<int>> ret;int n = nums.size();for(int st = 0; st < (1 << n); st++)  // 枚举 0~(2^n - 1){vector<int> tmp;  // 存储子集for(int i = 0; i < n; i++){// 如果 st 对应的二进制的第 i 位为 1,那么就放进子集中if((st >> i) & 1) tmp.push_back(nums[i]);}ret.push_back(tmp);}return ret;}
};

2. 费解的开关

【题目链接】

P10449 费解的开关 - 洛谷

【题目描述】

你玩过“拉灯”游戏吗?

25 25 25 盏灯排成一个 5 × 5 5 \times 5 5×5 的方形。

每一个灯都有一个开关,游戏者可以改变它的状态。

每一步,游戏者可以改变某一个灯的状态。

游戏者改变一个灯的状态会产生连锁反应:和这个灯上下左右相邻的灯也要相应地改变其状态。

我们用数字 1 1 1 表示一盏开着的灯,用数字 0 0 0 表示关着的灯。

下面这种状态

10111
01101
10111
10000
11011

在改变了最左上角的灯的状态后将变成:

01111
11101
10111
10000
11011

再改变它正中间的灯后状态将变成:

01111
11001
11001
10100
11011

给定一些游戏的初始状态,编写程序判断游戏者是否可能在 6 6 6 步以内使所有的灯都变亮。

【输入格式】

第一行输入正整数 n n n,代表数据中共有 n n n 个待解决的游戏初始状态。

以下若干行数据分为 n n n 组,每组数据有 5 5 5 行,每行 5 5 5 个字符。

每组数据描述了一个游戏的初始状态。

各组数据间用一个空行分隔。

【输出格式】

一共输出 n n n 行数据,每行有一个小于等于 6 6 6 的整数,它表示对于输入数据中对应的游戏状态最少需要几步才能使所有灯变亮。

对于某一个游戏初始状态,若 6 6 6 步以内无法使所有灯变亮,则输出 -1

【示例一】

输入

3
00111
01011
10001
11010
1110011101
11101
11110
11111
1111101111
11111
11111
11111
11111

输出

3
2
-1

【说明/提示】

测试数据满足 0 < n ≤ 500 0 < n \le 500 0<n500


(1) 解题思路

通过思考我们可以发现,按灯这样的操作具有以下三点 性质

  • 每一盏灯,最多只会按一次

这是因为一盏灯只有开或者关两个状态,所以当一盏灯被按两次的时候,与它不被按的情况是等价的;被按三次的时候,与它被按一次是等价的,以此类推。

  • 按灯的先后顺序,不会影响最终的结果

  • 第一行的按法确定了之后,后续灯的按法就跟着确定了

因为当第一行按了以后,假如说变成 00101,那么第二行只能去按第1, 2, 4 个位置才能使第一行的三个 0 变成 1,后面第三行和第四行是影响不到第一行的状态的。所以第一行确定了,那么其余行也就确定了。


有了这三点性质之后,我们就可以确定我们的解题思路了:

  1. 枚举出第一行的按法。(由于灯只有 1 和 0 两种状态,因此使用二进制枚举
  2. 根据第一行的按法,计算出第一行和第二行被按之后的新状态。
  3. 根据第一行的结果,推导出第二行的按法,第三、四、五行同理。
  4. 按完最后一行,判断所有灯是否全亮。

  • 如何枚举出第一行所有的按法?

由于每一行有五盏灯,所以二进制枚举的时候只需从 00000 枚举到 11111( 2 5 − 1 2^5 - 1 251)即可。0 代表这个位置不按,1 代表按。

  • 对于一个二进制数(按法),如何计算出有多少个 1(按了多少次)?

只需下面的方式即可:

// 计算二进制数中 1 的个数
int calc(int x)
{ int cnt = 0;while(x){cnt++;x &= x - 1;}return cnt;
}

可以在草稿纸上试一下,按位与几次就会拿掉几个 1。

  • 如何存储灯的初始状态?

我们可以用二进制来存储每一行的灯的开关状态,比如第一行的灯是 00110,那么我们只需要在一个一维数组中的第一位存储一个 00110 这个二进制数即可。

这里还有一个小技巧,就是我们可以把 1 转化成 0,0 转化成 1,这样反过来存储,问题就变成了把灯全部变成 0 要多少步,这样的话,当我们判断是否是全灭的时候,仅需看最后一行对应存储的值是否为 0 即可。当然这样转化还有其他好处,一会儿会提到。

  • 如何根据一个按灯方式 push,计算出当前行 a[i] 以及下一行 a[i + 1] 被按之后的状态?

对于 a[i] 行来说,我们可以用位运算快速计算得到。对于一行灯的状态 a[i] 如 10111,给定一种按灯方式 push 如 00100,我们仅需使用 a[i] = a[i] ^ push ^ (push << 1) ^ (push >> 1) 就可以让对应位置及其左右两边的灯的状态改变,因为 1、0 这两个数异或 1 就可以实现 1 变 0,0 变 1。

但是这里有一个小小的问题的,就是 push << 1 可能会让第 5 位变成 1,这是一个非法的位置,可能会影响后续的判断,因此我们需要截断高位。这个时候只需让计算后的 a[i] 按位与上 111110 即可,即 (1 << 5) - 1

所以计算 a[i] 的公式为:a[i] = a[i] ^ push ^ (push << 1) ^ (push >> 1) & ((1 << n) - 1))

计算第 a[i + 1] 行就比较简单了,只需要修改 push 中对应为 1 的位置,不需要管左右,易得 a[i + 1] = a[i + 1] ^ push

  • 如何求下一行怎么按?

我们的问题已经转化成了变成全灭,因此我们的 a[i + 1] 的需要按的位置只能是上一行 a[i] 亮着的位置,恰好就是 a[i],即 next_push = a[i]。这也是将问题转化成全灭的好处之一。


(2) 代码实现

#include<iostream>
#include<cstring>using namespace std;const int N = 10;
int n = 5;
int a[N];  // 存储每一行灯的状态
int t[N];  // a数组的备份// 计算一个二进制数中有多少个 1
int calc(int x)
{int cnt = 0;while(x){cnt++;x &= (x - 1);}return cnt;
}int main()
{int T; cin >> T;while(T--){// 有多组测试用例时记得清空上一轮的数据memset(a, 0, sizeof(a));// 读入数据for(int i = 0; i < n; i++){for(int j = 0; j < n; j++){char ch; cin >> ch;// 正常情况下应该是: if(ch == '1') a[i] |= 1 << j;// 现在我们反着来存if(ch == '0') a[i] |= 1 << j;}}int ans = 0x3f3f3f3f;  // 记录最终需要的最小操作数// 二进制枚举出第一行每一种情况for(int st = 0; st < (1 << n); st++){memcpy(t, a, sizeof(a));int push = st;  // 当前情况下的按法int cnt = 0;  // 当前情况下所需的最小操作数// 从上到下按每一行for(int i = 0; i < n; i++){cnt += calc(push);  // 计算每一行按了多少次t[i] = t[i] ^ push ^ (push << 1) ^ (push >> 1);  // 计算当前行按之后的状态t[i] &= (1 << n) - 1;  // 清除影响t[i + 1] ^= push;  // 计算下一行按之后的状态push = t[i];  // 下一行的按法}if(t[n - 1] == 0) ans = min(ans, cnt);  // 如果能全灭,更新最小操作数}if(ans > 6) cout << -1 << endl;else cout << ans << endl;}return 0;
}

3. even parity

【题目链接】

UVA11464 Even Parity - 洛谷

【题目描述】

给你一个 n × n n \times n n×n 01 01 01 矩阵(每个元素非 0 0 0 1 1 1),你的任务是把尽量少的 0 0 0 变成 1 1 1,使得原矩阵便为偶数矩阵(矩阵中每个元素的上、下、左、右的元素(如果存在的话)之和均为偶数)。

【输入格式】

输入的第一行为数据组数 T T T T ≤ 30 T \le 30 T30)。每组数据:第一行为正整数 n n n 1 ≤ n ≤ 15 1 \le n \le 15 1n15);接下来的 n n n 行每行包含 n n n 个非 0 0 0 1 1 1 的整数,相邻整数间用一个空格隔开。

【输出格式】

对于每组数据,输出被改变的元素的最小个数。如果无解,输出 − 1 -1 1

【示例一】

输入

3
3
0 0 0
0 0 0
0 0 0
3
0 0 0
1 0 0
0 0 0
3
1 1 1
1 1 1
0 0 0

输出

Case 1: 0
Case 2: 3
Case 3: -1

(1) 解题思路

这道题与上一道题很相似,我们也可以发现当我们把第一行的改变情况确定了之后,那么后面的改变状态也随之确定。因此,还是可以枚举第一行所有的改变情况。

  • 如何枚举出第一行所有的按法?

同上道题一样,我们从 0 枚举到 (1 << n) - 1。但是需要注意的是,不是所有的情况都是合法的,因为这道题只能从 0 变成 1,因此我们还需要判断每一行的最终情况是否合法。如果都是 0 变 1,则合法,计数;如果不合法,跳出本次循环,枚举下一个状态。

  • 如何根据一个改变方式 change,计算出下一行 a[i + 1] 的状态?

如果一个 a[i] 的某一个位置被改变后,我们需要计算 a[i + 1] 对应的位置,也就是 a[i] 的正下方,需要这个位置的上下左右数字之和为偶数,也就是上下左右的 0 的个数为偶数或者 1 的个数为偶数。由异或的性质可知,这个位置的上下左右的异或结果为 0,求下方的数实际上就是上左右三个数的异或结果。所以有 a[i + 1] = a[i - 1] ^ (a[i] << 1) ^ (a[i] >> 1)


(2) 代码实现

#include<iostream>
#include<cstring>using namespace std;const int N = 20;
int n;
int a[N];
int t[N];int calc(int x, int y)  // t[i], change
{int sum = 0;for(int j = 0; j < n; j++){// 如果 t[i] 的第 j 位是 1 而 change 的第 j 位是 0 则不合法,返回-1if(((x >> j) & 1) == 1 && ((y >> j) & 1) == 0) return -1;// 如果 t[i] 的第 j 位是 0 而 change 的第 j 位是 1 则合法,并统计一次次数if(((x >> j) & 1) == 0 && ((y >> j) & 1) == 1) sum++;}return sum;
}int solve()
{int ret = 0x3f3f3f3f;  // 记录最终所需的最小操作数// 枚举第一行的所有改变情况for(int st = 0; st < (1 << n); st++){memcpy(t, a, sizeof(a));int change = st;  // 每一行的改变情况int cnt = 0;  // 记录当前情况下的最小操作数bool flag = true;for(int i = 1; i <= n; i++){int c = calc(t[i], change);  // 判断是否合法,若合法则计算操作次数if(c == -1)  // 如果不合法{flag = false;break;}// 如果合法cnt += c;  // 统计改变次数t[i] = change;  // 当前行的最终状态change = t[i - 1] ^ (t[i] >> 1) ^ (t[i] << 1);  // 下一行的状态change &= (1 << n) - 1;  // 清除影响}if(flag) ret = min(ret, cnt);}if(ret == 0x3f3f3f3f) return -1;else return ret;
}int main()
{int T; cin >> T;for(int k = 1; k <= T; k++){memset(a, 0, sizeof(a));cin >> n;// 用二进制来读入数据for(int i = 1; i <= n; i++){for(int j = 0; j < n; j++){int x; cin >> x;if(x) a[i] |= 1 << j;}}printf("Case %d: %d\n", k, solve());}return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/news/908929.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

利用ngx_stream_return_module构建简易 TCP/UDP 响应网关

一、模块概述 ngx_stream_return_module 提供了一个极简的指令&#xff1a; return <value>;在收到客户端连接后&#xff0c;立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量&#xff08;如 $time_iso8601、$remote_addr 等&#xff09;&a…

Java如何权衡是使用无序的数组还是有序的数组

在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…

学习日记-day24-6.8

完成内容&#xff1a; 知识点&#xff1a; 1.网络编程_TCP编程 ### 编写客户端1.创建Socket对象,指明服务端的ip以及端口号 2.调用socket中的getOutputStream,往服务端发送请求 3.调用socket中的getInputStream,读取服务端响应回来的数据 4.关流public class Client {public…

JavaScript 核心对象深度解析:Math、Date 与 String

JavaScript 作为 Web 开发的核心语言&#xff0c;提供了丰富的内置对象来简化编程工作。本文将深入探讨三个重要的内置对象&#xff1a;Math、Date 和 String&#xff0c;通过详细的代码示例和综合案例帮助你全面掌握它们的用法。 一、Math 对象 Math 对象提供了一系列静态属…

HarmonyOS开发:设备管理使用详解

目录 前言 设备管理概述 设备管理组成 1、电量信息 &#xff08;1&#xff09;导入模块 &#xff08;2&#xff09;属性信息 &#xff08;3&#xff09;常用属性 &#xff08;4&#xff09;使用示例 2、设备信息 &#xff08;1&#xff09;导入模块 &#xff08;2&a…

el-select下拉框 添加 el-checkbox 多选框

效果 vue <el-select v-model"value" multiple style"width: 100%" popper-class"select-popover-class" placeholder"请选择试验项目"><el-option v-for"item in options" :key"item.value" :value&qu…

Memory Repair (三)

Top-Level Verification and Pattern Generation 本节涵盖 fuse box 编程、顶层 BISR&#xff08;内置自修复&#xff09;验证以及生产测试 pattern 的生成 Fuse Box Programming 通过 BISR controller 对 fuse box 进行编程的两种方法如下&#xff1a; • 采用 Autonomous mod…

通过Wrangler CLI在worker中创建数据库和表

官方使用文档&#xff1a;Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后&#xff0c;会在本地和远程创建数据库&#xff1a; npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库&#xff1a; 现在&#xff0c;您的Cloudfla…

谷歌aab怎么转 apk

一、环境搭建&#xff1a; 1、搭建 java 环境&#xff1b;2、安装 AndroidStudio&#xff1b;3、下载 bundletool&#xff08;地址&#xff1a;Releases google/bundletool GitHub&#xff09;&#xff1b;4、确定本地有没有签名文件&#xff0c;mac电脑一般在/users/ 自己的…

04-初识css

一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…

AWS EKS 集群日志上报观测云实践

AWS Lambda 介绍 AWS Lambda 是亚⻢逊提供的⼀种⽆服务器计算服务。它允许开发⼈员在⽆需管理服务器的情况下运⾏代码。AWS Lambda 基于事件驱动的模型&#xff0c;当触发指定的事件时&#xff0c;Lambda 会⾃动执⾏相应的代码逻辑。 Amazon CloudWatch 日志 CloudWatch 日志…

浏览器指纹科普 | 端口扫描保护是什么?

&#x1f50d; 什么是“端口”&#xff1f; 每台电脑都像一个办公大楼&#xff0c;端口就像是不同的房间号。不同软件&#xff08;比如浏览器、代理、远程控制工具&#xff09;会用不同的端口来“对外沟通”。 比如&#xff1a; 浏览网页可能用端口 80 或 443 用代理软件或某…

傲软录屏:轻松录制,高效分享

在数字内容创作和在线教育日益流行的今天&#xff0c;屏幕录制已成为许多人表达创意、分享知识的重要方式。无论是制作教学视频、记录游戏过程&#xff0c;还是进行远程会议记录&#xff0c;一款简单易用且功能强大的屏幕录制软件都是不可或缺的。傲软录屏正是这样一款能够满足…

小程序查广州楼盘网签数据和备案价(免费)

目录 一、网签数据/销控表查询二、备案价和不利因素查询三、如何体验 一、网签数据/销控表查询 二、备案价和不利因素查询 三、如何体验 #广州楼盘备案价查询 #网签数据查询 #广州买房必看攻略 #小程序查广州楼盘备案价

【HarmonyOS5】UIAbility组件生命周期详解:从创建到销毁的全景解析

⭐本期内容&#xff1a;【HarmonyOS5】UIAbility组件生命周期详解&#xff1a;从创建到销毁的全景解析 &#x1f3c6;系列专栏&#xff1a;鸿蒙HarmonyOS&#xff1a;探索未来智能生态新纪元 文章目录 前言生命周期全景图详细状态解析与最佳实践&#x1f3ac; Create状态&#…

【云计算系统】云计算中的计算几何

一、云计算系统中的几何算法 云计算系统在资源调度、空间数据处理、安全加密及大规模优化等场景中广泛运用几何算法以提升效率与精度。 空间数据处理与索引算法 ​空间索引算法(R树、四叉树)​​ ​作用​:高效管理地理空间数据(如地图坐标、三维点云),支持快速范围查询…

基于物联网技术设计的设计室内宠物监护系统

目录 项目开发背景设计实现的功能项目硬件模块组成设计思路系统功能总结技术方案使用的模块的技术详情介绍预期成果总结 1. 项目开发背景 随着科技的不断进步&#xff0c;物联网&#xff08;IoT&#xff09;技术逐渐渗透到生活中的各个方面&#xff0c;尤其在智能家居领域&am…

aurora与pcie的数据高速传输

设备&#xff1a;zynq7100&#xff1b; 开发环境&#xff1a;window&#xff1b; vivado版本&#xff1a;2021.1&#xff1b; 引言 之前在前面两章已经介绍了aurora读写DDR,xdma读写ddr实验。这次我们做一个大工程&#xff0c;pc通过pcie传输给fpga&#xff0c;fpga再通过aur…

产品经理入门到精通:01需求调研

一、需求调研 1、需求&#xff1a;用户在某些方面需要得到某种帮助以达成目的。 2、调研&#xff1a;通过一些方法来了解某件事情的真相&#xff0c;也可以叫调查研究。 3、需求调研&#xff1a;通过观察、访谈和体验等方式&#xff0c;探究事物本质的过程。是需求诞生的开始…

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…