蓝桥杯历届试题-九宫重排 + map学习笔记


题目链接: 蓝桥杯历届试题-九宫重排

题目描述

如下面第一个图的九宫格中,放着 1~8 的数字卡片,还有一个格子空着。与空格子相邻的格子中的卡片可以移动到空格中。经过若干次移动,可以形成第二个图所示的局面。
九宫格示意图
我们把第一个图的局面记为:12345678.
把第二个图的局面记为:123.46758
显然是按从上到下,从左到右的顺序记录数字,空格记为句点。
本题目的任务是已知九宫的初态和终态,求最少经过多少步的移动可以到达。如果无论多少步都无法到达,则输出-1。
输入格式:
输入第一行包含九宫的初态,第二行包含九宫的终态。 例如:
12345678.
123.46758
输出格式:
输出最少的步数,如果不存在方案,则输出-1。

题目分析

本题可以考虑搜索的方式解决。九宫格形式上是将空格周围的数字方块移动到空格的位置,但实际可以理解为将空格与周围某一个方块相互交换,即可获得搜索的分支方式。
在搜索过程中,所搜索到的结果位于树中的深度,恰好可以代表该结果所需的最小移动步数,因此可以推出,本题应当使用BFS进行解决。
由此可知,本题具体的方法为,将初始状态存入队列,每次循环中,列首元素出列,然后将该状态可以移动获得且未出现的元素入列,直到队列为空或者找到九宫的终态,前者即表示所求终态不可到达,后者在搜索树中深度即所需步数。
对于本题中输入的内容,如果将字符串代表的状态用一个二维数组进行存储直观模拟,则需要多次字符串与数组的转换,毫无疑问,所需代价极大。因此,可以考虑换一个思路,每次直接对该字符串中‘.’与其他字符相交换,对于交换进行一定限制避免出现非法结果。具体实现思路如下:

  1. ‘.’字符每次交换位置+3、+1、-1、-3,表示空格下移、右移、左移、上移;
  2. 如果‘.’字符交换结束后在字符串中坐标大于8或者小于0,则超界,非法;
  3. 如果‘.’字符坐标为0、3、6,则此时空格在九宫格最左侧,此时不可左移,坐标为2、5、8同理不可右移。

通过该方式模拟,可以实现各种情况均使用字符串表示。字符串表示的结果,同样可以借助于建立map实现string类型与int类型的映射检查结果是否重复,最终实现BFS。
另外,本题中检查,树深度为31,而遍历情况数为18万,通过数量级可知BFS可行。

解题代码

#include<cstdio>
#include<iostream>
#include<map>
#include<string>
#include<queue>
using namespace std;
typedef map<string, int> s_to_int;
typedef struct node {
	string s;
	int depth;
}node;
queue<node> q;
string s1,s2;
s_to_int visit;
int turn[4] = { 1,-1,3,-3 };//表示空白块各方向移动时字符'.'所移动相对位置。
int check(int ii, int place);
int main()
{
	int i, flag, dep, place;
	string s3, s4;
	node first, temp, add;
	char tmp;
	cin >> s1 >> s2;
	first.depth = 0;
	first.s = s1;
	q.push(first);	//初态入列
	visit[first.s] = 1;
	flag = 0;
	while (!q.empty()) {
		temp = q.front();
		q.pop();	//获取现态,现态出列
		dep = temp.depth;
		s3 = temp.s;
		for (i = 0; i < 4; i++) {	//向四个方向分别移动获取次态
			for (int ii = 0; ii < s3.size() ; ii++) {
				if(s3[ii]=='.')place = ii;	//获取'.'位置
			}
			if (!check(i, place))continue;	//非法移动,跳过
			s4 = s3;
			tmp = s4[place];
			s4[place] = s4[place + turn[i]];
			s4[place + turn[i]] = tmp;	//合法移动,进行字符串字符交换
			if (visit.count(s4)==0) {
				visit[s4] = 1;	//记录该状态已遍历,避免重复
				add.s = s4;
				add.depth = dep + 1;	//次态深度即现态深度+1
				q.push(add);	//次态入列
			}
			if (s4 == s2) {
				cout << add.depth << endl;	//找到九宫格终态,输出所需的最少移动次数
				flag = 1;	//表明终态可找到
				break;
			}
		}
		if (flag == 1)break;
	}
	if (flag == 0)cout << -1 << endl;	//终态不可实现,输出-1
	return 0;
}
int check(int i, int place) //检查本次移动是否合法,合法返回1,否则返回0
{
	if (place + turn[i] > 8 || place + turn[i] < 0)return 0;
	else if (i == 0) {
		if (place == 2 || place == 5 || place == 8)return 0;
	}
	else if (i == 1) {
		if (place == 0 || place == 3 || place == 6)return 0;
	}
	return 1;
}

学习笔记(已经了解C++中map的同学可以退出了)

最开始做题时候,感觉应该要使用BFS解决,但是各个状态如何存储,如何检查重复搜索,没有什么思路。本蒟蒻学过C++,但好像没学(上课真的听不懂),容器map知识约等于0。之后问大佬,大佬指导可以考虑用map解决,于是稍微学习了一下map相关的知识,然后试着用map解题,还好没啥语法报错(大佬:你怎么map都不知道?我:


这里是map的一点学习笔记,大佬们可以点击左上角退出了。

1、map简介

map是STL(中文标准模板库)的一个关联容器。

  1. 可以将任何基本类型映射到任何基本类型。如int array[100]事实上就是定义了一个int型到int型的映射。
  2. map提供一对一的数据处理,key-value键值对,其类型可以自己定义,第一个称为关键字,第二个为关键字的值
  3. map内部是自动排序的

2、使用map

使用map,必须引入map类所在的头文件:

#include<map>	//注意没有.h

map对象为模板类,需要关键字和存储对象两个模板参数:

map<type1name,type2name> maps;//第一个是键的类型,第二个是值的类型

例如,建立字符串到int型数据的映射,可以如下定义:

map<string,int> list;

之后即可获得该类型的map对象。为了简化对象创建,可以对其进行类型定义:

typedef map<string, int> s_to_int;
s_to_int list;

3、插入元素与元素访问

由于map已经对[]运算符进行了重载,插入元素可以使用如下方式:

list["123"]=123;

该方式比较直观,但存在性能问题。插入"123"时,先在list中查找键值为"123"的项,没有,于是插入"123",同时设置"123"的值为缺省值,之后再赋值为123。该过程存在多次设置缺省值后再赋值,元素为类对象时开销较大。可以使用函数insert()避免:

list.insert(s_to_int :: value_type("123",123));

而访问元素时,同样可以借助[]运算符:

int i;
i=list["123"];

但假如list中不存在该键值,则会自动插入该实例,值为初始化值。访问前可以使用find()或count()检查键值。list.find(key)返回类型是迭代器,成功查找时返回键值为key的迭代器,否则返回指向list尾部的迭代器。

s_to_int::iterator it=list.find("123");
if(it==list.end()){
	...//未找到
}
else {
	...//找到
}

4、删除元素

删除元素用erase()进行。删除指定元素方式:

it=list.find("123");
list.erase(it);	//迭代器删除

int n=list.erase("123");	//如果刪除了返回1,否则返回0

list.erase(list.begin(),list.end());
//使用迭代器清空list,list.clear()同作用

5、map其他函数

map.size():获取map中映射次数

int len;
len=list.size();//返回list中映射个数

maps.begin():返回指向map头部的迭代器
maps.end():返回指向map末尾的迭代器

//使用迭代器
s_to_int::iterator it;
for(it = list.begin(); it != list.end(); it++)
    cout<<it->first<<" "<<it->second<<endl;//输出key 和value值

maps.rbegin():返回指向map尾部的逆向迭代器
maps.rend():返回指向map头部的逆向迭代器

//使用迭代器
s_to_int::reverse_iterator it;
for(it = list.rbegin(); it != list.rend(); it++)
    cout<<it->first<<" "<<it->second<<endl;//输出key 和value值

maps.empty()判断其是否为空,空值返回1,非空返回0。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
青蛙跳杯子是蓝桥杯历届试题中的一道典型题目。题目要求模拟一个青蛙跳杯子的游戏,给定n个杯子和m次操作,每次操作可以选择一个杯子,并将其放置在当前位置前面或者后面的位置。最后,要求输出经过m次操作后,杯子从左到右的顺序。 下面是一个可能的Java代码实现: ```java import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(); // 杯子的数量 int m = scanner.nextInt(); // 操作的次数 int[] cups = new int[n]; // 初始化杯子的初始顺序 for (int i = 0; i < n; i++) { cups[i] = i+1; } // 执行m次操作 for (int i = 0; i < m; i++) { int index = scanner.nextInt(); // 选择的杯子的位置 int direction = scanner.nextInt(); // 移动的方向,1表示前面,2表示后面 // 将选择的杯子移动到指定位置 if (direction == 1) { for (int j = index-1; j > 0; j--) { int temp = cups[j]; cups[j] = cups[j-1]; cups[j-1] = temp; } } else if (direction == 2) { for (int j = index-1; j < n-1; j++) { int temp = cups[j]; cups[j] = cups[j+1]; cups[j+1] = temp; } } } // 输出最终的杯子顺序 for (int i = 0; i < n; i++) { System.out.print(cups[i] + " "); } } } ``` 该代码首先读取输入的杯子数量n和操作次数m,然后创建一个数组用来保存杯子的顺序。从1到n依次填充数组的初始顺序。接下来,执行m次操作,根据输入的位置和方向调整杯子的顺序,最后输出调整后的杯子顺序。 需要注意的是,该代码没有进行输入合法性的验证,实际应用中可能需要对输入进行适当的验证和处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值