P1032 [NOIP2002 提高组] 字串变换 / AcWing190. 字串变换

[NOIP2002 提高组] 字串变换

题目描述

已知有两个字串 A , B A,B A,B 及一组字串变换的规则(至多 6 6 6 个规则),形如:

  • A 1 → B 1 A_1\to B_1 A1B1
  • A 2 → B 2 A_2\to B_2 A2B2

规则的含义为:在 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} Bxyz

变换规则为:

  • abc → xu \texttt{abc}\rightarrow\texttt{xu} abcxu ud → y \texttt{ud}\rightarrow\texttt{y} udy y → yz \texttt{y}\rightarrow\texttt{yz} yyz

则此时, A A A 可以经过一系列的变换变为 B B B,其变换的过程为:

  • abcd → xud → xy → xyz \texttt{abcd}\rightarrow\texttt{xud}\rightarrow\texttt{xy}\rightarrow\texttt{xyz} abcdxudxyxyz

共进行了 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),达到了惊人的六千多万的复杂度,基本上过不了,所以为了优化用了双向广搜。

相关文章

显卡天梯图2024最新版,显卡是电脑进行图形处理的重要设备,...
初始化电脑时出现问题怎么办,可以使用win系统的安装介质,连...
todesk远程开机怎么设置,两台电脑要在同一局域网内,然后需...
油猴谷歌插件怎么安装,可以通过谷歌应用商店进行安装,需要...
虚拟内存这个名词想必很多人都听说过,我们在使用电脑的时候...