藍橋杯七段碼(遞迴、並查集連通性判斷)
1. 問題描述:
本題為填空題,只需要算出結果後,在程式碼中使用輸出語句將所填結果輸出即可。小藍要用七段碼數碼管來表示一種特殊的文字。
上圖給出了七段碼數碼管的一個圖示,數碼管中一共有 7 段可以發光的二極體,分別標記為 a, b, c, d, e, f, g。小藍要選擇一部分二極體(至少要有一個)發光來表達字元。在設計字元的表達時,要求所有發光的二極體是連成一片的。
例如:b 發光,其他二極體不發光可以用來表達一種字元。
例如:c 發光,其他二極體不發光可以用來表達一種字元。
這種方案與上一行的方案可以用來表示不同的字元,儘管看上去比較相似。
例如:a, b, c, d, e 發光,f, g 不發光可以用來表達一種字元。
請問,小藍可以用七段碼數碼管表達多少種不同的字元?
【答案提交】
這是一道結果填空的題,你只需要算出結果後提交即可。
本題的結果為一個整數,在提交答案時只填寫這個整數,填寫多餘的內容將無法得分。
來源:https://www.lanqiao.cn/problems/595/learning/
2. 思路分析:
① 分析題目可以知道如果沒有題目中所有亮的燈都要連在一起的條件那麼這道題目就很簡單了,因為每個燈存在兩種狀態:亮與不亮,所以燈的情況總共有2^7=128種,所以當存在這個限制條件的時候就需要排除一些不符合題目的情況。因為存在亮與不亮兩種狀態所以可以使用遞迴解決,當七盞燈的情況確定之後那麼就需要判斷亮的這些燈是否連在一起。判斷是否連在一起的特點可以知道這是一個連通性的判斷問題,對於連通性判斷
② 使用並查集判斷連通性其實理解之後也還是比較簡單,主要涉及到並查集的兩個操作(查詢與合併操作),查詢當前節點x的父節點與合併當前x, y的父子關係(當x節點的父節點不等於y節點的父節點的時候進行合併),查詢當前節點x的父節點可以使用遞迴的方法查詢,並且在查詢當前節點的父節點的時候對根節點往下連線的節點進行統一的聯絡,通過下面的圖就可以很清楚理解了,主要是將所有具有聯絡節點的父節點統一指向一個父節點,這樣就可以查找出最終的那個父節點。
並查集的查詢與合併操作,合併前需要先查詢當前兩個節點x,y的父節點,當father[x] != father[y]的時候進行合併,下面是具體的程式碼,其實理解之後記住即可(一般都是這樣的操作)
def union(self, x: int, y: int, father: List[int]):
fa = self.find(x, father)
fb = self.find(y, father)
if fa != fb:
father[fa] = fb
return
def find(self, x: int, father: List[int]):
if x == father[x]: return x
father[x] = self.find(father[x], father)
return father[x]
並查集其實有兩種寫法,不同的點主要是根據一開始的時候初始化father陣列的值的不同,第一種寫法是初始化father陣列為本身位置的值,也即father[i] = i。第二種寫法是一開始的時候初始化所有的father陣列值全為0。其中第一種初始化father陣列是常規的寫法,感覺第二種寫法會更加簡單一點。下面舉一個藍橋杯合根植物的例子來說明一下,一開始的時候初始化father陣列全為0,下面的例子可以宣告一個長度為20的陣列,我們其實在查詢的時候輸入(1,5),(5,9),(9,10),(10,12),(12,16)...左邊的聯通塊的時候,實際上是修改了1,5,6,10,11,12,16,18的父節點,最後17這個節點的父節點沒有修改也就是0,所以可以知道只要是具有聯絡的兩個節點的父節點都是被修改了的,直到到達最後的那個節點,因為最後那個節點沒有與其他的節點進行連續了所有父節點是為0的,所以我們最終只需要判斷出father陣列中節點為0的個數即可知道連通塊的數目,使用這個方法進行判斷是非常快速而且簡單的,只需要判斷一開始的時候宣告全為0的father陣列,然後查詢所有具有關係的父節點,最終通過計算father中為0的數目就為連通塊的數目。
第二種方法其實也是類似的,因為father陣列初始化的時候為father[i] = i,所以在查詢父節點的時候也是修改了具有聯絡節點的父節點,只有最終的那個父節點是沒有被修改的,最後那個父節點就為本身,所以我們只需要判斷出父節點為本身的節點的數目即為連通塊的數目
③ 這道題目其實一個很妙的點是利用了二維列表的下標關係來表示燈的連通情況,我們就可以通過最終亮的燈來判斷燈是否連線在一起
# 字母a的連通情況
graph[0][1] = graph[0][5] = 1
# 字母b的連通情況
graph[1][0] = graph[1][2] = graph[1][6] = 1
# 字母c的連通情況
graph[2][1] = graph[2][6] = graph[2][3] = 1
# 字母d的連通情況
graph[3][2] = graph[3][4] = 1
# 字母e的連通情況
graph[4][5] = graph[4][3] = graph[4][6] = 1
# 字母f的連通情況
graph[5][0] = graph[5][4] = graph[5][6] = 1
# 字母g的連通情況
graph[6][5] = graph[6][1] = graph[6][2] = graph[6][4] = 1
兩種不同初始化father陣列的方法在查詢的時候也略微有點不同:
3. 程式碼如下:
from typing import List
import collections
class Solution:
res = 0
def solve(self, lights: List[int], index: int, graph: List[List[int]]):
self.dfs(lights, 0, graph)
return self.res
# 合併x, y節點
def union(self, x: int, y: int, father: List[int]):
fa = self.find(x, father)
fb = self.find(y, father)
if fa != fb:
father[fa] = fb
return
# 遞迴查詢x節點的父節點
def find(self, x: int, father: List[int]):
if x == father[x]: return x
father[x] = self.find(father[x], father)
return father[x]
def dfs(self, lights: List[int], index: int, graph: List[List[int]]):
if index == len(lights):
father = [i for i in range(7)]
t = list()
# 使用一個列表來記錄所有亮的燈的下標這樣才可以判斷燈的連通情況,
# 後面通過才可以通過所有亮的情況進行連通
for i in range(7):
if lights[i]: t.append(i)
for i in range(len(t)):
for j in range(i + 1, len(t)):
# t[i]與t[j]兩個亮的燈有連通關係那麼就合併起來
if graph[t[i]][t[j]] == 1:
self.union(t[i], t[j], father)
count = 0
for i in range(len(t)):
# 判斷當前的節點是否是自己如果是自己那麼表示當前的節點就是最終的父節點
if father[t[i]] == t[i]: count += 1
s = ""
# 輸出燈亮的情況對應的字串
for i in range(len(lights)):
if lights[i] == 1:
s += chr(i + ord("a"))
print(s)
# 判斷是否只有一個連通塊
if count == 1:
self.res += 1
return
# 當前的燈為亮
lights[index] = 1
self.dfs(lights, index + 1, graph)
# 回溯, 令當前燈為不亮
lights[index] = 0
self.dfs(lights, index + 1, graph)
if __name__ == '__main__':
graph = [[0] * 7 for i in range(7)]
graph[0][1] = graph[0][5] = 1
graph[1][0] = graph[1][2] = graph[1][6] = 1
graph[2][1] = graph[2][6] = graph[2][3] = 1
graph[3][2] = graph[3][4] = 1
graph[4][5] = graph[4][3] = graph[4][6] = 1
graph[5][0] = graph[5][4] = graph[5][6] = 1
graph[6][5] = graph[6][1] = graph[6][2] = graph[6][4] = 1
lights = [0] * 7
print(Solution().solve(lights, 0, graph))
from typing import List
import collections
class Solution:
res = 0
def solve(self, lights: List[int], index: int, graph: List[List[int]]):
self.dfs(lights, 0, graph)
return self.res
def union(self, x: int, y: int, father: List[int]):
fa = self.find(x, father)
fb = self.find(y, father)
if fa != fb:
father[fa] = fb
return
def find(self, x: int, father: List[int]):
# 這裡判斷的是當前節點的父節點是否為0, 因為在一開始的時候就聲明瞭father陣列元素全為0
if father[x] == 0: return x
father[x] = self.find(father[x], father)
return father[x]
def dfs(self, lights: List[int], index: int, graph: List[List[int]]):
# 說明七盞燈的情況已經確定好了
if index == len(lights):
father = [0] * 7
t = list()
for i in range(7):
if lights[i]: t.append(i)
for i in range(len(t)):
for j in range(i + 1, len(t)):
if graph[t[i]][t[j]] == 1:
self.union(t[i], t[j], father)
# print(father)
count = 0
for i in range(len(t)):
if father[t[i]] == 0: count += 1
s = ""
for i in range(len(lights)):
if lights[i] == 1:
s += chr(i + ord("a"))
if count == 1:
self.res += 1
return
lights[index] = 1
self.dfs(lights, index + 1, graph)
lights[index] = 0
self.dfs(lights, index + 1, graph)
if __name__ == '__main__':
graph = [[0] * 7 for i in range(7)]
graph[0][1] = graph[0][5] = 1
graph[1][0] = graph[1][2] = graph[1][6] = 1
graph[2][1] = graph[2][6] = graph[2][3] = 1
graph[3][2] = graph[3][4] = 1
graph[4][5] = graph[4][3] = graph[4][6] = 1
graph[5][0] = graph[5][4] = graph[5][6] = 1
graph[6][5] = graph[6][1] = graph[6][2] = graph[6][4] = 1
lights = [0] * 7
print(Solution().solve(lights, 0, graph))