
题目分析
问题描述
给定两个字符串 A 和 B,以及一组字符串变换规则(最多 6 个),要求通过应用这些规则将 A 转换为 B。每个规则的形式是 A₁ → B₁,表示可以在 A 中的子串 A₁ 替换为 B₁。目标是在 10 步(含)以内找到最少的变换步数,否则输出 "NO ANSWER!"。
示例分析
示例输入:
abcd xyz
abc xu
ud y
y yz
转换过程:
abcd → xu ud (应用 abc → xu)
xu ud → xy d (应用 ud → y)
xy d → xyz (应用 y → yz)
输出: 3
算法思路
核心思想:广度优先搜索(BFS)
- 状态表示:每个状态保存当前字符串和已用步数
- 队列处理:使用队列按层遍历所有可能的转换
- 剪枝优化:
- 记录已访问的字符串,避免重复处理
- 限制最大步数为 10
- 规则匹配:对当前字符串尝试所有规则,生成新状态
算法步骤
- 读取初始字符串 A 和目标字符串 B
- 读取所有转换规则
- 初始化队列,将初始状态 (A, 0) 加入队列
- 开始 BFS:
- 取出队首状态
- 如果当前字符串等于 B,返回当前步数
- 如果步数已达 10,跳过该分支
- 对每个规则,在当前字符串中查找所有可匹配的位置
- 生成新字符串,若未访问过则加入队列
- 如果队列为空仍未找到解,返回 "NO ANSWER!"
完整AC代码
#include <iostream>
#include <string>
#include <queue>
#include <unordered_set>
#include <vector>
using namespace std;
struct State {
string str;
int steps;
State(string s, int st) : str(s), steps(st) {}
};
int main() {
string A, B;
cin >> A >> B;
vector<pair<string, string>> rules;
string a, b;
while (cin >> a >> b) {
rules.emplace_back(a, b);
}
queue<State> q;
unordered_set<string> visited;
q.push(State(A, 0));
visited.insert(A);
while (!q.empty()) {
State current = q.front();
q.pop();
// 找到目标
if (current.str == B) {
cout << current.steps << endl;
return 0;
}
// 超过步数限制
if (current.steps >= 10) {
continue;
}
// 应用所有规则
for (const auto& rule : rules) {
const string& from = rule.first;
const string& to = rule.second;
size_t pos = 0;
// 查找所有匹配位置
while ((pos = current.str.find(from, pos)) != string::npos) {
// 生成新字符串
string newStr = current.str;
newStr.replace(pos, from.length(), to);
// 如果未访问过
if (visited.find(newStr) == visited.end()) {
visited.insert(newStr);
q.push(State(newStr, current.steps + 1));
}
// 继续查找下一个匹配位置
pos += from.length();
}
}
}
// 未找到解
cout << "NO ANSWER!" << endl;
return 0;
}
代码详解
1. 数据结构设计
struct State {
string str;
int steps;
State(string s, int st) : str(s), steps(st) {}
};
- State结构体:保存当前字符串和已用步数
- 队列q:用于BFS的状态队列
- 哈希表visited:记录已处理的字符串,避免重复
2. BFS核心逻辑
while (!q.empty()) {
State current = q.front();
q.pop();
// 找到目标
if (current.str == B) {
cout << current.steps << endl;
return 0;
}
// 超过步数限制
if (current.steps >= 10) {
continue;
}
// 应用所有规则
for (const auto& rule : rules) {
// ... 生成新状态 ...
}
}
- 终止条件:找到目标字符串或步数超限
- 规则应用:对每个规则尝试所有可能的替换位置
3. 规则匹配实现
size_t pos = 0;
while ((pos = current.str.find(from, pos)) != string::npos) {
string newStr = current.str;
newStr.replace(pos, from.length(), to);
if (visited.find(newStr) == visited.end()) {
visited.insert(newStr);
q.push(State(newStr, current.steps + 1));
}
pos += from.length();
}
- 查找所有匹配:使用find函数循环查找
- 替换操作:使用string::replace方法
- 去重处理:只有未访问过的状态才加入队列
关键优化点
1. 剪枝策略
- 步数限制:严格控制在10步以内
- 状态去重:使用哈希表记录已访问状态
2. 字符串处理
- 高效替换:利用string的内置方法
- 全位置匹配:确保不遗漏任何可能的替换位置
复杂度分析
时间复杂度
- 最坏情况:O(6^10)(6条规则最多应用10次)
- 实际运行:由于剪枝和题目数据水,可以通过测试
空间复杂度
- O(N):N为不同字符串状态的数量
测试用例验证
样例输入1
abcd xyz
abc xu
ud y
y yz
输出:
3
样例输入2
aaaaa bbabb
ab aab
ba baa
aa aaa
aaa aa
a aa
输出:
4
常见错误与解决
1. 超时问题
原因:未进行有效剪枝,导致搜索空间过大 解决:
- 严格限制步数不超过10
- 使用哈希表记录已访问状态
2. 遗漏解
原因:规则应用不彻底,遗漏某些替换位置 解决:确保对每个规则的每个可能匹配位置都进行处理
3. 重复计算
原因:相同状态被多次处理 解决:使用visited集合记录已处理状态
算法总结
本文提供的BFS解法能有效解决洛谷P1032问题,关键优势在于:
- 广度优先:保证找到的是最少步数解
- 高效剪枝:通过步数限制和状态去重优化性能
- 全面搜索:确保不遗漏任何可能的转换路径
- 代码健壮:处理了各种边界情况
这种基于状态空间的搜索方法适用于许多字符串转换问题,掌握其核心思想可以解决类似的问题。
🔥 关注我,解锁CSP-J/S竞赛全攻略 🔥
(每日更新高频考点 + 精选真题解析,助你轻松备赛!)
👇 点击关注 → 立即提升竞赛战力 👇
[https://blog.csdn.net/stillwatersss]

636

被折叠的 条评论
为什么被折叠?



