本文思路来源于:
2016蓝桥杯c/c++省赛7–剪邮票
此处有该题的视频讲解(顺便感谢一下算法启蒙大佬):
蓝桦枫玥–2016蓝桥杯c/c++省赛7–剪邮票
以下放该题的思路和代码, 强烈建议先看懂该题, 再分析七段码, 会该题的大佬可以直接跳过~
该题是对之前13年蓝桥杯题目剪格子的直接回应: 剪格子只考虑了 “L” 形 (只有两个端点) 的连通情况, 从一个端点搜到另一个端点符合条件就直接退出了。而忽略了 “T” 和 “十” 字形 (多个端点) , 而剪邮票使用的方法兼顾了这两种情况。
而该题是一个无向图的情况, 类似于剪邮票的多个端点情况, 所以完全可以搬过来。
具体思路为:
- 用一维数组和next_permutation去列出所有的情况, 然后映射到二维数组。
- 对二维数组进行检查, 只要遇到一个符合的块, 就去深搜所有的连通块,全部变成0, cnt++。
- 最后如果cnt=1说明只有一个连通块。
剪邮票完整代码ps: 因为该题采取了从1置0的方法, 所以不需要传统dfs的visit数组去判断某一点是否搜索过
#include
using namespace std;
#define MAX 12
// 该方法完美兼容"T"字形和"十"字形
int a[MAX] = {0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1};
// 映射数组
int b[3][4] = {0};
// 搜索顺序: 上左右下
int u[4] = {-1, 0, 0, 1};
int v[4] = {0, -1, 1, 0};
// 将该点所有连通点全部置0
void dfs(int x, int y)
{
b[x][y] = 0;
// 按照上左右下的顺序进行搜索
// 一个完整的过程为:将所有的区域置为0后, 退回开始点
for (int i = 0; i < 4; i++)
{
int xx = x + u[i];
int yy = y + v[i];
// 若该点未越界并且还没有遍历
if (xx >= 0 && xx < 3 && yy >= 0 && yy < 4 && b[xx][yy] == 1)
dfs(xx, yy);
}
}
bool check()
{
// 数组映射
for (int i = 0; i < 3; i++)
for (int j = 0; j < 4; j++)
b[i][j] = a[4 * i + j];
int cnt = 0;
// 当区域为连通块时, 搜索后数组全部置为0, 则后面的循环全部不通过if语句
// 若cnt>1, 说明存在不止一个区域
for (int i = 0; i < 3; i++)
for (int j = 0; j < 4; j++)
{
// 搜索到符合条件的点
if (b[i][j] == 1)
{
dfs(i, j);
cnt++;
}
}
if (cnt == 1)
return true;
return false;
}
int main()
{
int ans = 0;
do
{
if (check())
ans++;
} while (next_permutation(a, a + 12));
cout << ans;
return 0;
}
然后是七段码
小蓝要用七段码数码管来表示一种特殊的文字。
七段码上图给出了七段码数码管的一个图示,数码管中一共有7 段可以发光的二极管,分别标记为a, b, c, d, e, f, g。小蓝要选择一部分二极管(至少要有一个)发光来表达字符。在设计字符的表达时,要求所有发光的二极管是连成一片的。
例如:b 发光,其他二极管不发光可以用来表达一种字符。
例如:c 发光,其他二极管不发光可以用来表达一种字符。这种方案与上一行的方案可以用来表示不同的字符,尽管看上去比较相似。
例如:a, b, c, d, e 发光,f, g 不发光可以用来表达一种字符。
例如:b, f 发光,其他二极管不发光则不能用来表达一种字符,因为发光的二极管没有连成一片。
请问,小蓝可以用七段码数码管表达多少种不同的字符?
该题的思路跟上题大同小异, 只不过从一个二维的数组深搜变成了一个无向图, 而且不止连通5个。
为了方便理解, 我们与上题一样进行三步走。
1.初始化无向图, 用一维数组和next_permutation列出所有的情况, 然后映射到另一个一维数组
// 无向图数组
int a[MAX][MAX] = {0};
// 亮灯数组
int b[MAX] = {0};
// 映射数组
int c[MAX] = {0};
// 初始化图
void init()
{
// 表示两个位置相邻
a[0][1] = 1;
a[0][5] = 1;
a[1][2] = 1;
a[1][6] = 1;
a[2][3] = 1;
a[2][6] = 1;
a[3][4] = 1;
a[4][5] = 1;
a[4][6] = 1;
a[5][6] = 1;
}
// 可能亮1至7段
for (int i = 0; i < MAX; i++)
{
// 初始化亮灯数组
memset(b, 0, sizeof(b));
// *注释1
for (int j = 7; j >= i; j--)
{
b[j] = 1;
}
do
{
// 如果确实是一段
if (check())
sum++;
} while (next_permutation(b, b + MAX));
}
*注释1:
此处注意一个小问题: next_permutation的全排列中的1一定要从后面往前加, 如果初始化为{1,1,1,0,0,0,0}, 则全排列只有这一种情况, 必须初始化为{0,0,0,0,1,1,1}。
2.check函数检查该状态是否符合条件, 搜索一个连通点, 然后去深搜它的相邻点, 将它们全部变成0, 同时flag++, 若flag=1, 说明是单连通块, 否则就是多个连通块。
bool check()
{
// 数组映射
for (int i = 0; i < MAX; i++)
{
c[i] = b[i];
}
int flag = 0;
for (int i = 0; i < MAX; i++)
{
// 如果i位置亮灯
if (c[i])
{
// 把他周围的灯全部灭掉
dfs(i);
// 连通块数+1
flag++;
}
}
// 如果全连着, 也就是上述if语句只符合了一次
if (flag == 1)
return true;
return false;
}
3.dfs函数进行深搜, 若搜索到i点与k点相连, 则递归进入i点。
// 深搜与k相邻的点并且关掉
void dfs(int k)
{
// 把k位置关掉
c[k] = 0;
for (int i = 0; i < MAX; i++)
{
// 若i位置与k位置相连
if (a[i][k] || a[k][i])
{
// 若i位置亮着灯
if (c[i])
{
// 深搜
dfs(i);
}
}
}
}
最后放上完整代码
七段码完整代码 #include
#define MAX 7
using namespace std;
// 相邻数组
int a[MAX][MAX] = {0};
// 亮灯数组
int b[MAX] = {1, 1, 0, 1, 1, 1, 0};
// 映射数组
int c[MAX] = {0};
// 初始化图
void init()
{
a[0][1] = 1;
a[0][5] = 1;
a[1][2] = 1;
a[1][6] = 1;
a[2][3] = 1;
a[2][6] = 1;
a[3][4] = 1;
a[4][5] = 1;
a[4][6] = 1;
a[5][6] = 1;
}
// 深搜相邻的点并且关掉
void dfs(int k)
{
// 把k位置关掉
c[k] = 0;
for (int i = 0; i < MAX; i++)
{
// 若i位置与k位置相连
if (a[i][k] || a[k][i])
{
// 如果i位置亮着灯
if (c[i])
{
// 深搜
dfs(i);
}
}
}
}
bool check()
{
// 数组映射
for (int i = 0; i < MAX; i++)
{
c[i] = b[i];
}
int flag = 0;
for (int i = 0; i < MAX; i++)
{
// 如果i位置亮灯
if (c[i])
{
// 把他周围的灯全部灭掉
dfs(i);
// 连通块数+1
flag++;
}
}
// 如果全连着
if (flag == 1)
return true;
return false;
}
int main()
{
init();
int sum = 0;
// 可能亮1至7段
for (int i = 0; i < MAX; i++)
{
// 初始化亮灯数组
memset(b, 0, sizeof(b));
for (int j = 7; j >= i; j--)
{
b[j] = 1;
}
do
{
// 如果确实是一段
if (check())
sum++;
} while (next_permutation(b, b + MAX));
}
cout << sum;//结果为80
}