[NOIP2002 提高组] 字串变换
题目描述
已知有两个字串 A , B A,B A,B 及一组字串变换的规则(至多 6 6 6 个规则),形如:
- A 1 → B 1 A_1\to B_1 A1→B1。
- A 2 → B 2 A_2\to B_2 A2→B2。
规则的含义为:在 A A A 中的子串 A 1 A_1 A1 可以变换为 $ B_1 , , ,A_2$ 可以变换为 B 2 ⋯ B_2\cdots B2⋯。
例如: A = abcd A=\texttt{abcd} A=abcd, B = xyz B=\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。
则此时, A A A 可以经过一系列的变换变为 B B B,其变换的过程为:
- abcd → xud → xy → xyz \texttt{abcd}\rightarrow\texttt{xud}\rightarrow\texttt{xy}\rightarrow\texttt{xyz} abcd→xud→xy→xyz。
共进行了 3 3 3 次变换,使得 A A A 变换为 B B B。
输入格式
第一行有两个字符串 A , B A,B A,B。
接下来若干行,每行有两个字符串 A i , B i A_i,B_i Ai,Bi,表示一条变换规则。
输出格式
若在
10
10
10 步(包含
10
10
10 步)以内能将
A
A
A 变换为
B
B
B,则输出最少的变换步数;否则输出 NO ANSWER!
。
样例 #1
样例输入 #1
abcd xyz
abc xu
ud y
y yz
样例输出 #1
3
提示
对于 100 % 100\% 100% 数据,保证所有字符串长度的上限为 20 20 20。
【题目来源】
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),达到了惊人的六千多万的复杂度,基本上过不了,所以为了优化用了双向广搜。