在 4 × 4 方阵中填字,使下面 6 条路径各自构成一个成语,且 6 个成语互不相同:
样例字典里有这 6 个成语恰好能拼成一个合法方阵:
| 路径 | 方向 | 成语 |
|---|---|---|
| ①第一行 | → | 花 香 鸟 语 |
| ②末列 | ↓ | 语 秒 添 下 |
| ③末行 | ← | 下 里 八 人 |
| ④首列 | ↑ | 人 面 桃 花 |
| ⑤主对角 | ↘ | 花 前 月 下 |
| ⑥副对角 | ↙ | 语 出 惊 人 |
所以四个角是 A=花, D=语, P=下, M=人,答案为 1。
把每个汉字当作图的结点,每个成语当作首字→尾字的一条有向边。6 条路径 = 6 条边;4 个角 = 4 个结点:
注意 ①②③④ 围成一个有向四边形 A→D→P→M→A,而 ⑤⑥ 是它的两条对角线。
用 map<string,int> 给每个汉字串分配整数 id,后续全部用 int 比较。
G[{u,v}] 存所有首=u 尾=v 的成语下标,查找 O(1)。
外层枚 ①、再从 byFirst[D] 枚 ②、再 byFirst[P] 枚 ③ —— 4 个角全部固定!
④⑤⑥ 的首尾都已知,直接从 G[{M,A}]、G[{A,P}]、G[{D,M}] 取候选。
表面是 6 重循环,但后 5 层每层都被"首字 / 尾字"严格卡住——候选桶通常只有 0~几个元素。N = 10⁴ 的真实词典实测飞快,远到不了 N³。
#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;
}
vector 大小一般 ≤ 3,实测毫秒级。
continue 排除已选。G[N][N] 二维数组:汉字种类可能数万,会爆内存。请用 map。long long:N = 10⁴ 时方阵数可能很大。