上图给出了七段码数码管的一个图示,数码管中一共有 77 段可以发光的二 极管,分别标记为 a, b, c, d, e, f, ga,b,c,d,e,f,g。
小蓝要选择一部分二极管(至少要有一个)发光来表达字符。在设计字符 的表达时,要求所有发光的二极管是连成一片的。
例如:bb 发光,其他二极管不发光可以用来表达一种字符。
例如 cc 发光,其他二极管不发光可以用来表达一种字符。这种方案与上 一行的方案可以用来表示不同的字符,尽管看上去比较相似。
例如:a, b, c, d, ea,b,c,d,e 发光,f, gf,g 不发光可以用来表达一种字符。
例如:b, fb,f 发光,其他二极管不发光则不能用来表达一种字符,因为发光 的二极管没有连成一片。
请问,小蓝可以用七段码数码管表达多少种不同的字符?
运行限制
- 最大运行时间:1s
- 最大运行内存: 128M
对于并查集可以参考一下这篇文章
https://zhuanlan.zhihu.com/p/93647900
解题过程加部分解析
#include <iostream>
using namespace std;
int e[10][10];
int ans=0;
int st[10];
int p[10];
int find(int x)
{
if(p[x]!=x)
{
// find(p[x]);err
p[x]=find(p[x]);
//光找父节点是不够的,还要将此时的节点换成父节点才行
//p用于存放和接收、返回最终结果.
//首先先从1开始,一直找,1-2-6
//p[6]=6,return 返回,p[2]接收p[6]
//再由p[2]返回,p[1]接收,最终得到p[1]=6;
}
// else
//???
//为什么加else会出错
//理解程序
//dfs是一直向下搜索的直到搜索到结果位置
//如1-2-6,6为结果,那么p[6]=6的结果就要返回给
//p[2],p[2]=6,然后p[2]已知才能返回给p[1]
//于是p[1]=6
//如果你加了else,那就没有办法返回给p[2],p[1],就出错了
return p[x];
}
void dfs(int u)
{
if(u==8)
{
for(int i=1;i<=7;i++)
{
p[i]=i;
}
for(int i=1;i<=7;i++)
for(int j=1;j<=7;j++)
{
if(st[i]&&st[j]&&e[i][j])
{
p[find(i)]=find(j);
//p用于储存上一次的父节点,find(j)寻找是否有新的父节点
//每一次的改变父节点,也就是节点相连
//就表示两个管的相连
//全部都连在一个父节点就表示所有两个管相连的合并
//即所有管相连的表示。
//如果所有的管都能相连,那最终肯定是只有一个父节点的
//(因为一个父节点的时候,所有开始的父节点相连变成了节点
//连接在最后的那个父节点上)
//只有有灯管不能相连,才能有多个父节点
//(因为不能相连,就无法改变那个初始的父节点,那个父节点一直都是独立的)
}
}
int cnt=0;
for(int i=1;i<=7;i++)
if(st[i]&&p[i]==i)
{
cnt++;
}
if(cnt==1)
{
ans++;
// return;err//不要写在if里面,你无论增不增加都是要退出函数的
//不然就死循环了!
}
return;
}
st[u]=1;
dfs(u+1);
st[u]=0;
dfs(u+1);
}
int main()
{
e[1][2]=e[1][6]=1;
e[2][1]=e[2][7]=e[2][3]=1;
e[3][2]=e[3][7]=e[3][4]=1;
e[4][3]=e[4][5]=1;
e[5][4]=e[5][7]=e[5][6]=1;
e[6][1]=e[6][7]=e[6][5]=1;
e[7][6]=e[7][2]=e[7][3]=e[7][5]=1;
dfs(1);
cout<<ans<<endl;
return 0;
}
//dfs的return一般是不加条件的!放在末尾退出或者返回值
//不要随便加条件干预!放结尾直接return就好
详细解析
#include <iostream>//并查集
using namespace std;
const int N = 10;
int ans;
int p[N];
bool st[N];
int e[N][N];
//合并(Union):把两个不相交的集合合并为一个集合。
//查询(Find):查询两个元素是否在同一个集合中。
int find(int x)
{
if (p[x] != x)
p[x] = find(p[x]);
return p[x];
}//查询,访问父节点
//只要当前值不是父节点,就改到当前的父节点
//我们用递归的写法实现对代表元素的查询:一层一层访问父节点,
//直至根节点(根节点的标志就是父节点是本身)。
//要判断两个元素是否属于同一个集合,只需要看它们的根节点是否相同即可。
//集合思想.判断是否在一个集合之中
//前面只有满足可以连接的if,才能够改到当前的父节点或更新父节点
//这样到了最后,也就是最后一个父节点的时候,就是所有节点的根
//即所有情况的出发点,从这个父节点展开的所有情况
//指向一个父节点能说明什么?
//每一个开始的父节点,代表不同的数码管,每一次的连接,相当于探索不同的路径。
//指向表示这种情况的联通,如p[1]=2时,就表示,a管连到b管的联通情景
//类推可知,每一次更新父节点的时候,就表示当前管与新管的联通情景
//把左右两边都看作是父节点,不要当成赋值!
//也是集合的意思,集合就包含了1,2,6,因此1,2指向6这个父节点,就表示他们都在这个集合里面,
//就表示了1,2,6相连的情况--只要我找到了父节点,那就十分清楚我之前的情况是什么样的.
void dfs(int u)
{
if (u == 8)//全部灯泡都有了该有的状态
{
for (int i = 1; i <= 7; i++)
p[i] = i;//父节点
//必须是p[i]=i才为父节点否则都只是节点
//一开始每个灯都是独立的,都已自己为父节点,所以要建立起连接.
//if和for的遍历是数据来源,并查集是合并相同情况,以及避免不相连的情况。
//for是遍历所有情况,find就是为了判断情况是重复的还是新的情况。也就是合并.
//新的就加入集合中作为最大的父节点,相同的就赋值相同的父节点就等于不变
for (int i = 1; i <= 7; i++)//作为开头的管
for (int j = 1; j <= 7; j++)//遍历所有可以联通的分支管
if (e[i][j] && st[i] && st[j])
//更换父节点就是为了表示不同的情况,只有条件满足才会更换父节点。
// 是否联通 两个灯是否都亮着
p[find(i)] = find(j);//合并集合操作
//首先要理解find,find是查找父节点
//父节点就是所有节点的根,其他节点都是分支
//先找到两个集合的代表元素,然后将父节点的前者设为后者即可。
// why?
// 不用关心最终父节点的值,我们只要知道只有唯一才是正确的。
//每次都查找开头的管当前父节点是多少,然后查找分支管的父节点是多少
// 相同就覆盖表示元素都在一个集合中重复不再计入,不同就表示不在一个集合更新父节点纳入集合之中
//合并的理解:相同集合的就直接重叠直接被覆盖了,如2,1的时候,与1,2重复
//那么2,1的时候就知道最大的父节点是6,i在6的集合里面,所以是p[6],而j此时为1
//同样也在6的集合里,所以p[6]=6不变,就合并成1种情况了.
//find会找到当前元素的父节点,因此只要是相连的节点
// 都是只有唯一的一个父节点的。因此,如果当前所有的管都相连
// 那也必定只有唯一的一个头结点。如果有多个,则说明一定有不相连的管存在
// 此时情况不满足题意就舍去。
//find是为了判断元素是否在同一个集合,不在就是新的节点,然后相连
// 相连的最终都会在一个集合里,只有没有连接过的才是独立的.
//find(i)还是find(j)都会更新p[x],更新集合父节点。
//p[]起到存储数据作用,存放不同节点的当前父节点,find(j)是为了查找新的当前父节点.
//前面的节点换成后面还是后面换成前面不重要,最重要的是他们能够连接并且最终只有唯一的父节点
int cnt = 0;
for (int i = 1; i <= 7; i++)
if (st[i] && p[i] == i) //判断有几个联通块
//灯亮 查找有多少个父节点
//联通块的意思就是相连的部分,只有一个才符合要求
// 如12345678所有相连才满足,即一个父节点
// 如果123456,78出现两部分,就代表有不相连的部分,就是两个父节点
// 两个联通块,就不满足题意
cnt++;
if (cnt == 1)
//只有唯一父节点就说明只有一个连通块
//有多个父节点就表示有路不能走通,即这种情况下的灯泡会有路是不相连的,
//即不满足条件,那么就会有多个父节点,因为有些父节点根本更改不了
//因此,一般情况下最后都只有一个父节点,多个说明不满足题意.
ans++;
return;
}
st[u] = 1; // 打开第 u 个二极管
dfs(u + 1);
st[u] = 0; // 关闭第 u 个二极管
dfs(u + 1);
//dfs思路及总结
//每一次进入循环,都要讨论这种情况下是打开第u个还是关闭,
//一开始就从全部打开开始算起,判断完后就看看关闭时什么情况
//执行完后就结束全部打开(u==7)的情况,然后到打开6个灯的下一步,也就是关闭u==6的状态
//同样只要所有的灯状态都还不明确就进不了循环内,于是又是继续深搜又是讨论
//下一个灯打开还是关闭的状态。后面的依次类推
// 关键就是u=8,代表的就是所有灯泡的状态已经明确的时候才开始考虑结果是否合法
// 我就设条件必须满足所有灯泡状态必须明确才能开始讨论,就遍历了所有情况
// u不到8,dfs就会继续,每次都分两种情况,这就实现了所有情况的遍历
// 其实也可以换种思维,这是程序执行的思维
// 可以直接联想,每一次的u都是两种情况,打开或者关闭,然后就进入到下一个u
// 继续给出两种情况,一直到u=8明确所有灯泡状态。用情景思维更好理解
//
// 自己脑中有棵树,每次都有两个分支,每个分支里也有两个分支,一直分下去就是了
// 这就是所有情况的讨论.
//程序遍历所有结果一般都是把每一轮的所有情况考虑完,最后因为递推还是循环就能推广到所有可能
}
int main()
{//首先,要满足连成一片的条件,必须要相连条件才能成立
e[1][2] = e[1][6] = 1;//将一个点第一步可能到达的所有情况连接起来
//如1,2为从a-b,1,6为a-f
e[2][1] = e[2][7] = e[2][3] = 1;
e[3][2] = e[3][7] = e[3][4] = 1;
e[4][3] = e[4][5] = 1;
e[5][4] = e[5][7] = e[5][6] = 1;
e[6][1] = e[6][7] = e[6][5] = 1;
e[7][2] = e[7][3] = e[7][5] = e[7][6] = 1;
//虽然分成了7个小部分,但是会有重复交叉,所以要合并去除相同情况.
//使用并查集
dfs(1);
//情况复杂讨论一般都用dfs
//方向多一般用bfs
cout << ans << endl;
return 0;
}