Blocks

GitHub跳转原题关系图返回列表

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:

  1. 选择一块未使用的积木;
  2. 选择这块积木上的一个字母;
  3. 把新字符串加入集合;
  4. 继续递归,直到长度为 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 记录已经使用过的积木,就能清楚地枚举所有合法拼法。