DFS 枚举所有积木使用顺序和字母选择,预生成可拼单词集合后直接查询。
OJ: usaco
题目 ID: 1205
难度:入门
标签:搜索枚举字符串usaco
日期: 2026-07-11 17:46
题意
有 4 块积木,每块积木有 6 个字母。
拼一个单词时,每个字母必须由一块不同的积木提供,一块积木最多使用一次。
给出若干个长度为 1 到 4 的单词,判断每个单词能否拼出。
思路
先看一个逐词搜索的暴力:
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-07-11 17:46
* update_at: 2026-07-11 17:47
*/
// brute.cpp:小数据暴力解,用来帮助理解题意并辅助对拍。
#include <bits/stdc++.h>
using namespace std;
string block_letters[4];
string target_word;
bool used_block[4];
bool block_has_char(int block_id, char c) {
for (int i = 0; i < 6; i++) {
if (block_letters[block_id][i] == c) {
return true;
}
}
return false;
}
// 第 pos 层为单词第 pos 个字母选择一块尚未使用的积木。
bool dfs_match(int pos) {
if (pos == (int)target_word.size()) {
return true;
}
for (int i = 0; i < 4; i++) {
if (used_block[i]) {
continue;
}
if (!block_has_char(i, target_word[pos])) {
continue;
}
used_block[i] = true;
if (dfs_match(pos + 1)) {
return true;
}
used_block[i] = false;
}
return false;
}
bool can_spell(string word) {
target_word = word;
for (int i = 0; i < 4; i++) {
used_block[i] = false;
}
return dfs_match(0);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
for (int i = 0; i < 4; i++) {
cin >> block_letters[i];
}
for (int i = 1; i <= n; i++) {
string word;
cin >> word;
if (can_spell(word)) {
cout << "YES\n";
} else {
cout << "NO\n";
}
}
return 0;
}这个暴力把每个单词的位置看成一层选择:第 pos 层为单词第 pos 个字母选择一块尚未使用、且包含这个字母的积木。
满分代码可以更直接:提前生成所有能拼出的单词。
从空字符串开始 DFS:
- 选择一块未使用的积木;
- 选择这块积木上的一个字母;
- 把新字符串加入集合;
- 继续递归,直到长度为 4。
这样生成的所有非空字符串,就是这些积木可以拼出的全部单词。之后每个查询只需要判断它是否在集合中。
代码
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-07-11 17:46
* update_at: 2026-07-11 17:47
*/
#include <bits/stdc++.h>
using namespace std;
string block_letters[4];
set<string> can_make_words;
void dfs_build(int used_mask, string word) {
if (!word.empty()) {
can_make_words.insert(word);
}
if ((int)word.size() == 4) {
return;
}
for (int i = 0; i < 4; i++) {
if ((used_mask & (1 << i)) != 0) {
continue;
}
for (int j = 0; j < 6; j++) {
dfs_build(used_mask | (1 << i), word + block_letters[i][j]);
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
for (int i = 0; i < 4; i++) {
cin >> block_letters[i];
}
dfs_build(0, "");
for (int i = 1; i <= n; i++) {
string word;
cin >> word;
if (can_make_words.find(word) != can_make_words.end()) {
cout << "YES\n";
} else {
cout << "NO\n";
}
}
return 0;
}复杂度
积木数量固定为 4,每块 6 个面,预处理规模很小。
查询使用集合查找,复杂度可以视为常数。
总结
本题的重点是不要把同一块积木用在单词的多个位置。
用 used_mask 记录已经使用过的积木,就能清楚地枚举所有合法拼法。