B4351 [信息与未来 2025] 成语接龙讲义 · C++14

一、题意:6 条路径,1 个方阵

4 × 4 方阵中填字,使下面 6 条路径各自构成一个成语,且 6 个成语互不相同:

A D M P
① 第一行 → ② 末列 ↓ ③ 末行 ← ④ 首列 ↑ ⑤ 主对角 ↘ ⑥ 副对角 ↙
关键观察 ✨ 4 个(A, D, M, P)被多条路径共享;中间 12 格各自只属于一条路径——这是解题的突破口!

二、走一遍样例(N=10)

样例字典里有这 6 个成语恰好能拼成一个合法方阵:

路径方向成语
第一行花 香 鸟 语
末列语 秒 添 下
末行下 里 八 人
首列人 面 桃 花
主对角花 前 月 下
副对角语 出 惊 人

所以四个角是 A=花, D=语, P=下, M=人,答案为 1

三、建模:成语就是一条"边"

把每个汉字当作图的结点,每个成语当作首字→尾字的一条有向边。6 条路径 = 6 条边;4 个角 = 4 个结点:

A D P M

注意 围成一个有向四边形 A→D→P→M→A,而 是它的两条对角线

四、核心思路

1
字符串编号

map<string,int> 给每个汉字串分配整数 id,后续全部用 int 比较。

2
按 (首,尾) 装桶

G[{u,v}] 存所有首=u 尾=v 的成语下标,查找 O(1)。

3
枚举 3 条相邻边

外层枚 ①、再从 byFirst[D] 枚 ②、再 byFirst[P] 枚 ③ —— 4 个角全部固定!

4
剩下 3 条边直接查表

④⑤⑥ 的首尾都已知,直接从 G[{M,A}]G[{A,P}]G[{D,M}] 取候选。

为什么这样可行? 中间 12 格各属一条路径,互不交叉。角一旦固定,每条边都可以独立选择,只需保证 6 个成语两两不同。

复杂度

表面是 6 重循环,但后 5 层每层都被"首字 / 尾字"严格卡住——候选桶通常只有 0~几个元素。N = 10⁴ 的真实词典实测飞快,远到不了 N³。

五、参考实现(C++14)

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int n, w[10005][4];                    // w[i][0..3]: 第 i 个成语的 4 个字
map<string, int> id;                   // 字符串 → 编号
map<pair<int,int>, vector<int>> G;    // (首,尾) → 成语下标列表
map<int, vector<int>> byFirst;         // 首字   → 成语下标列表

int getId(const string& s) {
    if (!id.count(s)) id[s] = id.size() + 1;
    return id[s];
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    for (int i = 0; i < n; ++i) {
        string s;
        for (int j = 0; j < 4; ++j) { cin >> s; w[i][j] = getId(s); }
        G[{w[i][0], w[i][3]}].push_back(i);
        byFirst[w[i][0]].push_back(i);
    }

    ll ans = 0;
    for (int i1 = 0; i1 < n; ++i1) {                  // ① 第一行 A → D
        int A = w[i1][0], D = w[i1][3];

        for (int i2 : byFirst[D]) {                   // ② 末列  D → P
            if (i2 == i1) continue;
            int P = w[i2][3];

            for (int i3 : byFirst[P]) {               // ③ 末行  P → M
                if (i3 == i1 || i3 == i2) continue;
                int M = w[i3][3];

                for (int i4 : G[{M, A}]) {            // ④ 首列  M → A
                    if (i4 == i1 || i4 == i2 || i4 == i3) continue;
                    for (int i5 : G[{A, P}]) {        // ⑤ 主对角 A → P
                        if (i5 == i1 || i5 == i2 || i5 == i3 || i5 == i4) continue;
                        for (int i6 : G[{D, M}]) {    // ⑥ 副对角 D → M
                            if (i6 == i1 || i6 == i2 || i6 == i3 ||
                                i6 == i4 || i6 == i5) continue;
                            ++ans;
                        }
                    }
                }
            }
        }
    }
    cout << ans << '\n';
    return 0;
}
小贴士 6 层循环看着可怕,但后 5 层每次进入循环时,候选 vector 大小一般 ≤ 3,实测毫秒级。

六、易错点

  1. 6 个成语必须两两不同——每层都要 continue 排除已选。
  2. 方向不要搞反:末行右→左读、首列下→上读。
  3. 不要开 G[N][N] 二维数组:汉字种类可能数万,会爆内存。请用 map
  4. 答案要用 long long:N = 10⁴ 时方阵数可能很大。

七、一句话总结

🎯 把成语看作"首字→尾字"的有向边;枚举 3 条相邻边定下 4 个角;剩下 3 条边按 (首,尾) 查表 + 去重统计方案数。