[NOIP2002 提高组] 字串变换
题目描述
已知有两个字串 A,BA,BA,B 及一组字串变换的规则(至多 666 个规则),形如:
- A1→B1A_1\to B_1A1→B1。
- A2→B2A_2\to B_2A2→B2。
规则的含义为:在 AAA 中的子串 A1A_1A1 可以变换为 $ B_1,,,A_2$ 可以变换为 B2⋯B_2\cdotsB2⋯。
例如:A=abcdA=\texttt{abcd}A=abcd,B=xyzB=\texttt{xyz}B=xyz,
变换规则为:
- abc→xu\texttt{abc}\rightarrow\texttt{xu}abc→xu,ud→y\texttt{ud}\rightarrow\texttt{y}ud→y,y→yz\texttt{y}\rightarrow\texttt{yz}y→yz。
则此时,AAA 可以经过一系列的变换变为 BBB,其变换的过程为:
- abcd→xud→xy→xyz\texttt{abcd}\rightarrow\texttt{xud}\rightarrow\texttt{xy}\rightarrow\texttt{xyz}abcd→xud→xy→xyz。
共进行了 333 次变换,使得 AAA 变换为 BBB。
输入格式
第一行有两个字符串 A,BA,BA,B。
接下来若干行,每行有两个字符串 Ai,BiA_i,B_iAi,Bi,表示一条变换规则。
输出格式
若在 101010 步(包含 101010 步)以内能将 AAA 变换为 BBB,则输出最少的变换步数;否则输出 NO ANSWER!。
样例 #1
样例输入 #1
abcd xyz
abc xu
ud y
y yz
样例输出 #1
3
提示
对于 100%100\%100% 数据,保证所有字符串长度的上限为 202020。
【题目来源】
NOIP 2002 提高组第二题
思路
首先这是一道搜索题目,我们可以很容易看出来这是一个最小步数模型,即每一步都是一个点的bfs,我们只需要一步步遍历做bfs就好。
一般来说bfs的题目思路比较简单,但代码比较难实现
写法一 (双向广搜)
#include <iostream>
#include <algorithm>
#include <queue>
#include <unordered_map>
using namespace std;
typedef unordered_map<string, int> USI; // 为了后面方便定义
typedef queue<string> QS;
const int N = 6;
string a[N], b[N];
string A, B;
int n;
int expand(QS& q, USI& da, USI& db, string a[N], string b[N])
{
string t = q.front(); // 取出队头元素
q.pop();
for (int i = 0; i < t.size(); i ++ ) // 在长度内
for (int j = 0; j < n; j ++ )
if (t.substr(i, a[j].size()) == a[j]) // 如果两部分相等
{
string st = t.substr(0, i) + b[j] + t.substr(i + a[j].size()); // 前面到从0开始,长度是i,加上规则中的,再加上后半部分
if (da.count(st)) continue; // 如果做过,就跳过,算是一个剪枝优化吧
if (db.count(st)) return da[t] + 1 + db[st]; // 值是从起点到当前的t点,加上到当前点的1,然后加上从终点走到当前点的距离
da[st] = da[t] + 1; // 更新答案
q.push(st);
}
return 11; // 如果无解就返回一个比10大的数就行
}
int bfs()
{
QS qa, qb; // 定义两个队列,一个从起点开始,一个从终点开始
USI da, db; // 用来判重,这是第一种写法
qa.push(A), da[A] = 0; // 初始化起点,到起点的距离是0
qb.push(B), db[B] = 0; // 初始化终点,到终点的距离是0
while (qa.size() && qb.size()) // 两个队列中都有元素才会继续
{
int t;
// 每次先扩展元素较少的
if (qa.size() < qb.size()) t = expand(qa, da, db, a, b); // 运用规则
else t = expand(qb, db, da, b, a); // 如果是相反的,规则要反过来用
if (t <= 10) return t; // 在规定范围内就返回
}
return 11; // 如果无解返回一个比10大的数就好
}
int main()
{
cin >> A >> B;
while (cin >> a[n] >> b[n]) n ++; // 由于输入个数不确定所以用这种方法输入
if (A == B) // 在AcWing上需要特判
{
puts("0");
return 0;
}
int step = bfs();
if (step > 10) puts("NO ANSWER!"); // 题目中说了,只要多于10步即视为无解
else printf("%d\n", step);
return 0;
}
写法二(普通bfs)
//这一段代码AcWing过不了
// 与第一段代码相同部分不再赘述
#include <iostream>
#include <queue>
#include <algorithm>
#include <set>
#define step first // 方便理解
#define str second
using namespace std;
typedef pair<int, string> PIS;
const int N = 10;
string A, B;
string a[N], b[N];
int n = 1, res;
queue<PIS> q;
set<string> st; // 这里用第二种方法,set判重
int bfs()
{
PIS f, t, h;
q.push((PIS){0, A}); // 加入初始值
st.insert(A); // 此点已经遍历过了
while (!q.empty())
{
h = q.front();q.pop();
for (int i = 1; i <= n; i ++ )
{
f = h;
for (int j = h.str.find(a[i], 0); j <= h.str.length(); j = h.str.find(a[i], j + 1)) // 找到满足条件的替换方式
{
f = h;
t.step = f.step + 1;
t.str = f.str.replace(j, a[i].length(), b[i]); // 一样的道理,用了STL
if (t.step > 10) return -1; // 这里从大于10的数变成了-1
if (t.str == B) return t.step;
if (st.count(t.str)) continue;
st.insert(t.str); // 加入点
q.push(t);
}
}
}
return -1;
}
int main()
{
cin >> A >> B;
while (cin >> a[n] >> b[n]) n ++;
n -= 1;
res = bfs();
if (res == -1) puts("NO ANSWER!");
else printf("%d\n", res);
return 0;
}
这时就会有有人问了:为什么能用普通的bfs做却还要写双向广搜呢?
因为如果按照最坏情况算时间复杂度能达到O(6^10),达到了惊人的六千多万的复杂度,基本上过不了,所以为了优化用了双向广搜。
本文详细介绍了NOIP2002提高组的一道字串变换题目,涉及从字符串A通过最多6个规则变换到字符串B的最短步数求解。通过样例输入和输出展示问题,解析了如何使用双向广搜和普通BFS算法解决该问题,并探讨了两种方法的时间复杂度和优化策略。

268

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



