天天看點

第十一屆藍橋省賽 —— 七段碼解析 全排列 + DFS / 并查集判斷圖聯通七段碼

七段碼

小藍要用七段碼數位管來表示一種特殊的文字。

第十一屆藍橋省賽 —— 七段碼解析 全排列 + DFS / 并查集判斷圖聯通七段碼

上圖給出了七段碼數位管的一個圖示,數位管中一共有 7 段可以發光的二極管,分别标記為 a, b, c, d, e, f, g。

小藍要選擇一部分二極管(至少要有一個)發光來表達字元。在設計字元的表達時,要求所有發光的二極管是連成一片的。

  • 例如:b 發光,其他二極管不發光可以用來表達一種字元。
  • 例如:c 發光,其他二極管不發光可以用來表達一種字元。

這種方案與上一行的方案可以用來表示不同的字元,盡管看上去比較相似。

  • 例如:a, b, c, d, e 發光,f, g 不發光可以用來表達一種字元。
  • 例如:b, f 發光,其他二極管不發光則不能用來表達一種字元,因為發光的二極管沒有連成一片。

請問,小藍可以用七段碼數位管表達多少種不同的字元?

答案

80

第十一屆藍橋省賽 —— 七段碼解析 全排列 + DFS / 并查集判斷圖聯通七段碼

思路

考場上腦抽了,這一看就是圖的題,就想着用Map + List建圖,折騰了半個小時,發現這是無向圖,然後絕望了,在草稿紙上推了一下,亂填了個60…

後面發現其實很簡單,暴力就完了,填空題不求算法有多麼好,隻要能得到正确答案就行。雖然參加的是Java組的,但後面敲的是C++的解析,湊合着看吧~差不多的。吐槽考場辣雞Eclipse瘋狂報錯,提供的是JDK1.8卻隻能用JDK1.4編譯氣死了。

吐槽完了,說一下下面代碼的思路:

  • 首先初始化建圖(鄰接表有點麻煩,鄰接矩陣真香!)
  • 爆搜路徑長度1 - 7的所有對應的圖的路徑
  • 檢查路徑是不是連通的(DFS、并查集都可)
  • 輸出滿足條件的路徑個數

===== 21.02.19 update 并查集 =====

DFS + 并查集

#include <cstdio>
#include <string>
#include <vector>
#include <cstring>

using std::string;
using std::vector;
// 七段碼所有亮燈情況的存儲容器
vector<string> candidate;
// 鄰接矩陣存儲,真香
int graph[7][7];

// 基礎并查集
struct DisjointSet {
    int* father;

    explicit DisjointSet(int n) {
        father = new int[n];
        for (int i = 0; i < n; i++) {
            father[i] = i;
        }
    }

    // 找爸爸
    int findRoot(int x) {
        return father[x] == x ? x : father[x] = findRoot(father[x]);
    }

    // 合并
    bool merge(int x, int y) {
        int root_x = findRoot(x);
        int root_y = findRoot(y);

        if (root_x != root_y) {
            father[root_x] = root_y;
        }
        return root_x != root_y;
    }
};

/**
 * 鄰接矩陣初始化,1 代表連通
 */
void init() {
    // a : 0, b : 1, c : 2
    // d : 3, e : 4, f : 5, g : 6
    memset(graph, 0, sizeof(graph));
    // connect with 'a'
    graph[0][1] = graph[0][5] = 1;
    // connect with 'b'
    graph[1][0] = graph[1][2] = graph[1][6] = 1;
    // connect with 'c'
    graph[2][1] = graph[2][3] = graph[2][6] = 1;
    // connect with 'd'
    graph[3][1] = graph[3][4] = 1;
    // connect with 'e'
    graph[4][3] = graph[4][5] = graph[4][6] = 1;
    // connect with 'f'
    graph[5][0] = graph[5][4] = graph[5][6] = 1;
    // connect with 'g'
    graph[6][1] = graph[6][2] = graph[6][4] = graph[6][5] = 1;
}

/**
 * 爆搜亮燈的所有情況,換句話說:是要找出 "abcdefg" 中
 * 你所需要全排列。非常經典的題了,推薦去 Bilibili 搜尋 BV1qE411E7di
 * 并把這位 UP 主的 dfs系列視訊看完,去做對應的練習,你也能很快學會!
 * @param result 搜尋到的答案
 * @param end 搜尋邊界條件
 * @param index 目前搜尋的開始位置
 */
void dfs(string& result, int end, int index) {
    if (result.length() == end) {
        candidate.emplace_back(result);
        return;
    }

    for (int i = index; i < 7; i++) {
        result.push_back('a' + i);
        dfs(result, end, i + 1);
        result.pop_back();
    }
}

/**
 * 使用并查集去檢查每一種情況是否符合題目條件
 * 即判斷這個字元串對應的圖是否是連通圖
 * 一開始,我也不太了解這道題為啥可以用并查集做
 * 想了一會,回想起寫 dfs 判斷圖連通的“痛苦回憶”
 * 啊,原來并查集在這裡隻是用來輔助我們檢查圖是否連通的啊
 * 那沒事了,暴力就完了
 */
void process() {
    int ans = 0;

    // 周遊每一種候選情況
    for (auto str : candidate) {
        // 初始化并查集
        DisjointSet disj{ 7 };
        int len = str.length();
        // 這個雙重暴力就不用解釋了吧~
        // 用來檢查 str[i] 和 str[j] 對應的點是否是連通的
        // 如果連通,我們就把它們合并
        for (int i = 0; i < len - 1; i++) {
            for (int j = i + 1; j < len; j++) {
                if (graph[str[i] - 'a'][str[j] - 'a']) {
                    disj.merge(str[i] - 'a', str[j] - 'a');
                }
            }
        }
        // 如果這個圖是連通圖,那麼對應這張圖的所有點
        // 将隻會存在一個點是 disj.father[ch - 'a'] == ch - 'a'
        // 即隻有一個點是初始值狀态,若不是,則表示這張圖不連通
        int cnt = 0;
        for (auto ch : str) {
            if (disj.father[ch - 'a'] == ch - 'a') {
                cnt++;
            }
        }
        if (cnt == 1) {
            ans++;
        }
    }

    printf("%d\n", ans);
}

int main() {
    string result;

    init();
    // 全排列長度 1 - 7 的所有情況~
    // eg :
    // len = 1 -> "a" "b" "c" "d" "e" "f" "g"
    // len = 2 -> "ac" "ad" "ae" "ag" "bd" "be"...
    for (int i = 1; i <= 7; i++) {
        dfs(result, i, 0);
    }
    process();

    // hhh, 是不是要比 DFS 檢查圖聯通要簡潔呢?
    return 0;
}
           

雙重DFS

// 數字燈管
// 暴力枚舉 + dfs 判斷圖連通
#include <cstdio>
#include <vector>
#include <string>
#include <cstring>
#include <unordered_set>

using std::unordered_set;
using std::string;
using std::vector;
vector<char> graph[7];
bool used[7];

void initGraph() {
    // connect with 'a'
    graph[0].push_back('b');
    graph[0].push_back('f');

    // connect with 'b'
    graph[1].push_back('a');
    graph[1].push_back('c');
    graph[1].push_back('g');

    // connect with 'c'
    graph[2].push_back('b');
    graph[2].push_back('d');
    graph[2].push_back('g');

    // connect with 'd'
    graph[3].push_back('c');
    graph[3].push_back('e');

    // connect with 'e'
    graph[4].push_back('d');
    graph[4].push_back('f');
    graph[4].push_back('g');

    // connect with 'f'
    graph[5].push_back('a');
    graph[5].push_back('e');
    graph[5].push_back('g');

    // connect with 'g'
    graph[6].push_back('b');
    graph[6].push_back('c');
    graph[6].push_back('e');
    graph[6].push_back('f');
}

void dfs(vector<string>& ans, string& way, int len, int index) {
    if (way.size() == len) {
        ans.emplace_back(way);
        return;
    }

    for (int i = index; i < 7; i++) {
        way.push_back('a' + i);
        dfs(ans, way, len, i + 1);
        way.pop_back();
    }
}

void isConnected(unordered_set<char>& way, bool& flag, char vertex, int& cnt, int len) {
    if (cnt == len) {
        flag = true;
        return;
    }

    for (auto ch : graph[vertex - 'a']) {
        if (!used[ch - 'a'] && way.count(ch)) {
            used[ch - 'a'] = true;
            cnt++;
            isConnected(way, flag, ch, cnt, len);
        }
    }
}

int main() {

    initGraph();
    vector<string> ans_maybe;
    string way;

    for (int i = 1; i <= 7; i++) {
        dfs(ans_maybe, way, i, 0);
    }

    int cnt = 0;
    unordered_set<char> us;
    unordered_set<string> notAns;
    for (auto& way : ans_maybe) {
        memset(used, 0, sizeof(used));
        for (auto ch : way) {
            us.insert(ch);
        }
        bool flag = false;
        int vertexes = 1;
        used[way[0] - 'a'] = true;
        isConnected(us, flag, way[0], vertexes, way.length());
        if (flag) {
            cnt++;
            notAns.insert(way);
        }
        us.clear();
    }

    printf("%d\n", cnt);

    for (auto& s : ans_maybe) {
        if (!notAns.count(s)) {
            printf("%s\n", s.c_str());
        }
    }

    return 0;
}
           

繼續閱讀