七段码
题目描述
小蓝要用七段码数码管来表示一种特殊的文字。
上图给出了七段码数码管的一个图示,数码管中一共有 7 段可以发光的二 极管,分别标记为 a, b, c, d, e, f, g
小蓝要选择一部分二极管(至少要有一个)发光来表达字符。在设计字符 的表达时,要求所有发光的二极管是连成一片的。
例如: b 发光,其他二极管不发光可以用来表达一种字符。
例如: c 发光,其他二极管不发光可以用来表达一种字符。这种 方案与上 一行的方案可以用来表示不同的字符,尽管看上去比较相似。
例如: a, b, c, d, e 发光, f, g 不发光可以用来表达一种字符。
例如: b, f 发光,其他二极管不发光则不能用来表达一种字符,因为发光 的二极管没有连成一片。
请问,小蓝可以用七段码数码管表达多少种不同的字符?
答案提交
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
题解
相信大家跟我一样,看见这道题第一反应肯定时dfs(深度优先搜索算法)。
枚举完每一种组合的时间复杂度也就是O( 2 n 2^n 2n),而 n n n的值是固定的7,所以我们最后枚举的次数也不过就是 2 7 = 128 2^7=128 27=128,数据范围真的很小
因此初始的方向我们就已经固定了。
但是,我们该如何判断我们选择的二极管是连成一片的呢?这时候相信很多人都能想到连通图。
我们可以以每个二极管(从 a a a- g g g,标记为1-7)为结点,将相邻的两个结点使用无向边连接起来,然后我们只要判断所有被选出的点是否能构成一个连通图就行了
//建立边
edge[1][2] = edge[2][1] = true;
edge[1][6] = edge[6][1] = true;
edge[2][3] = edge[3][2] = true;
edge[2][7] = edge[7][1] = true;
edge[3][7] = edge[7][3] = true;
edge[3][4] = edge[4][3] = true;
edge[4][5] = edge[5][4] = true;
edge[5][7] = edge[7][5] = true;
edge[5][6] = edge[6][5] = true;
edge[6][7] = edge[7][6] = true;
好的,现在我们只需要判断所有选出的点是否能够构成一个连通图了,也就是,如果这些结点构成的连通图个数超过1个,那就是非连通的
那么为了判断连通图的个数,我们可以引入集合的概念,在集合中,我们不考虑结点间的关系,只考虑这些节点能不能互相连通,为了方便标记集合,我们在集合中寻找一个代表性的结点(一般是集合中从小到大的第一个结点)作为一个集合标志点,我们以这个点的编号作为整个集合的编号,那么所有的这个集合中的结点都以这个编号来标记自己属于这个集合
这个时候我们选择的这个代表性的结点像不像集合中的老大?
这时候我们向统计总共有多少个集合,只需要找到多少个结点是这个结合的老大(也就是自己以自己为老大,集合以自己的名字命名)就能得到集合的数目了
这就是并查集了(这里就粗略介绍一下,了解详情可以移步并查集)
详细代码
代码确实长,但是注释占了一半,而且为了便于理解把运行过程输出出来了,仔细看
package com.lanqiao;
import java.util.Arrays;
/**
* @author 王宇哲
* @date 2022/3/30 16:57
*/
public class 七段码 {
/**
* 标记当前状态下,每个数被选择的情况(选与不选)
*/
static boolean[] tags = new boolean[8];
/**
* 有多少中二极管的开启组合可以成立
*/
static int cnt = 0;
/**
* 存储无向图边的连接情况
*/
static boolean[][] edge = new boolean[8][8];
/**
* 存储某个选择状态下,每个结点的父节点
*/
static int[] father;
/**
* 递归获取当前结点的祖先节点,获取时顺便更新
* @param i 需要找到祖先节点的结点
* @return 当前结点的祖先结点
*/
static int getFather(int i){
//如果这个结点不以自己为父亲,就寻找并更新祖先
if(i!=father[i]){
return father[i]=getFather(father[i]);
}else{
return i;
}
}
/**
* 合并两个结点所在的集合
* @param a a结点
* @param b b结点
*/
static void merge(int a,int b){
//合并前说明一下情况
System.out.println("合并:"+a+"集合,"+b+"集合");
//让b的祖先认a的祖先为祖先
//说明一下,合并只是对祖先的合并,更新每个结点的祖先会在获取祖先的函数中同时进行
father[getFather(b)] = getFather(a);
}
/**
* 判断当前选择是否可以成功全部亮起
* @return
*/
static boolean judge(){
// 初始化,一开始每个结点都以自己为老大(祖先)
father = new int[8];
for(int i = 1 ;i <= 7;i++){
father[i] = i;
}
// 遍历每两个(被选择的)结点的组合,如果两个点可以连通,那他们就是一个集合的
for(int i = 1;i<=7;i++){
for(int j = i+1;j<=7;j++){
//判断当前情况i,j是否被选择,都被选择的话如果连通就合并集合
if(tags[i] && tags[j] && edge[i][j]){
merge(i,j);
}
}
}
//集合数量
int collectionNum = 0;
//判断被选中的该节点是不是集合老大,通过老大个数判断集合个数
for(int i = 1;i<=7;i++){
if(tags[i] && father[i] == i){
collectionNum++;
}
}
return collectionNum == 1;
}
/**
* 深度优先搜索
* @param num 当前选择的二极管
*/
static void dfs(int num){
// 二极管超过7个,所有二极管都经过了选与不选的操作,那么就可以进行判断连通状态了
if(num > 7){
// 如果全部可以连通,就把结果+1
if(judge()){
cnt++;
}
//输出所有被选择的二极管
for (int i = 1;i<=7;i++){
if(tags[i]){
System.out.print(i+" ");
}
}
System.out.println();
// 输出集合关系
System.out.println(Arrays.toString(father));
System.out.println("-------------");
return;
}
//不选这个二极管,往下递推
tags[num] = false;
dfs(num+1);
//选当前二极管以后再进行递推
tags[num] = true;
dfs(num+1);
}
public static void main(String[] args) {
//建立边
edge[1][2] = edge[2][1] = true;
edge[1][6] = edge[6][1] = true;
edge[2][3] = edge[3][2] = true;
edge[2][7] = edge[7][1] = true;
edge[3][7] = edge[7][3] = true;
edge[3][4] = edge[4][3] = true;
edge[4][5] = edge[5][4] = true;
edge[5][7] = edge[7][5] = true;
edge[5][6] = edge[6][5] = true;
edge[6][7] = edge[7][6] = true;
//深搜
dfs(1);
//输出结果
System.out.println(cnt);
}
}