单词接龙求最长拼接:预处理每对词的最小合法重叠,再以“最后词 + 各词用量”为状态记忆化搜索最大长度。
OJ: roj
题目 ID: 3502
难度:普及
标签:搜索记忆化字符串python
创建: 2026-10-02 04:27
更新: 2026-10-06 12:09
题目描述
beast+astonish→beastonish),不能整词包含(at 不能接 atide),每词最多用两次,求最长龙的长度。
输入第一行 5, at, touch, cheat, choose, tact, a → 输出 23。
思路
后续能接多长只取决于最后一个词和各词已用次数,与前面路径无关。先预处理每对单词的最小合法重叠(
参考代码
Python 版:
python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-02 04:27
# update_at: 2026-10-02 04:27
import sys
from functools import cache
def min_overlap(a: str, b: str) -> int | None:
"""a 后接 b 的最小合法重叠长度:k 从 1 试起,接不上返回 None。
k 只试到 min(len(a), len(b)) - 1,自动排除整词包含——如 at 接 atide
唯一能对上的重叠 k=2 恰好等于 len(at),所以两词不能相连。
"""
for k in range(1, min(len(a), len(b))):
if a[-k:] == b[:k]:
return k
return None
def solve() -> None:
data = iter(sys.stdin.read().split())
word_count = int(next(data))
words = [next(data) for _ in range(word_count)]
head = next(data)
# overlap[i][j]:词 j 接在词 i 后的最小合法重叠;None 表示接不上
overlap: list[list[int | None]] = [
[min_overlap(a, b) for b in words] for a in words # n^2 对
]
@cache
def remain(last: int, used: tuple[int, ...]) -> int:
"""词 last 收尾、各词已用次数为 used 时,还能追加的最大字符数。"""
best = 0
for j, k in enumerate(overlap[last]):
if used[j] == 2 or k is None:
continue # 用满两次或接不上
used_j = used[:j] + (used[j] + 1,) + used[j + 1:] # 只改第 j 位,保持 tuple 可哈希
gain = len(words[j]) - k # 重叠部分在龙里只计一次
best = max(best, gain + remain(j, used_j))
return best
# 龙头:任一以 head 开头的词做第一个词,整词计入长度
ans = 0
for i, w in enumerate(words):
if w[0] != head:
continue
used = [0] * word_count
used[i] = 1
ans = max(ans, len(w) + remain(i, tuple(used)))
print(ans)
if __name__ == "__main__":
solve()C++ 版(同一算法):
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-06 12:09
* update_at: 2026-10-06 12:09
*/
#include <algorithm>
#include <iostream>
#include <map>
#include <string>
using namespace std;
typedef long long ll;
const int MAXN = 25;
int n; // 单词数
string word[MAXN]; // word[i]:第 i 个单词(下标从 0 开始)
int len[MAXN]; // len[i]:第 i 个单词的长度
char head_char; // 龙开头的字母
int overlap[MAXN][MAXN]; // overlap[i][j]:词 j 接在词 i 后的最小合法重叠长度,-1 表示接不上
ll pow3[MAXN]; // pow3[i] = 3^i,用来把各词用量编码进一个整数
map<ll, ll> memo; // memo[last * pow3[n] + code]:还能追加的最大字符数
// 计算 a 后接 b 的最小合法重叠长度;k 只试到 min(len)-1,自动排除整词包含
int min_overlap(const string &a, int len_a, const string &b, int len_b) {
int lim = min(len_a, len_b);
for (int k = 1; k < lim; k++) {
if (a.compare(len_a - k, k, b, 0, k) == 0) {
return k; // 找到最小可行重叠,重叠部分在龙里只计一次
}
}
return -1; // 不允许零重叠直接拼接
}
// 词 last 收尾、各词已用次数由 code 的三进制位表示时,还能追加的最大字符数
ll dfs(int last, ll code) {
ll key = last * pow3[n] + code;
map<ll, ll>::iterator it = memo.find(key);
if (it != memo.end()) {
return it->second; // 相同状态只算一次
}
ll best = 0;
for (int j = 0; j < n; j++) {
if (overlap[last][j] < 0) {
continue; // 这两词接不上
}
ll digit = (code / pow3[j]) % 3;
if (digit == 2) {
continue; // 每个单词最多出现两次
}
ll next_code = code + pow3[j]; // 第 j 个词多用一个
ll gain = len[j] - overlap[last][j];
best = max(best, gain + dfs(j, next_code));
}
memo[key] = best;
return best;
}
int main() {
cin >> n;
for (int i = 0; i < n; i++) {
cin >> word[i];
len[i] = word[i].size();
}
cin >> head_char;
pow3[0] = 1;
for (int i = 1; i <= n; i++) {
pow3[i] = pow3[i - 1] * 3;
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
overlap[i][j] = min_overlap(word[i], len[i], word[j], len[j]);
}
}
// 龙头:任一以 head_char 开头的词做第一个词,整词长度都计入
ll ans = 0;
for (int i = 0; i < n; i++) {
if (word[i][0] != head_char) {
continue;
}
ll code = pow3[i]; // 第 i 个词用了一次
ans = max(ans, len[i] + dfs(i, code));
}
cout << ans << endl;
return 0;
}