2024年3月29号题解
魔板
解题思路
- 如果我们可以固定一个起点,那么我们把所有的走法都枚举了,那么就得到了可以走到的所有解
- 但题目给的起点并不是固定的,那么有没有一种函数(方法)可以把任意起点位置转换成一个固定的起点,然后终点也可以转换的呢
- 也就是说把一个起点和终点变成固定的其中的对应的终点
例如:
初状态:4 6 2 8 5 7 3 1
末状态:3 4 8 7 2 5 1 6
可以转换成:
初状态:1 2 3 4 5 6 7 8
末状态:7 1 4 6 3 5 8 2
那么我们现在需要的是转换之后的路径等于没有转换的路径,也就是说转换前的状态等于转换后状态。
其实我们只需要对任意起点状态重新进行编号就可以了
即起始状态中
4 ---> 1
6 ---> 2
2 ----> 3
8 ----> 4
5 ----> 5
7 ---->6
3 ----> 7
1 ----> 8
那么终点状态中
3 ----> 7(我们在起始状态中给3重新编号成为了7)
4 ----> 1(我们在起始状态中给4重新编号成为了1)
8 ----> 4(我们在起始状态中给8重新编号成为了4)
7 ----> 6(我们在起始状态中给7重新编号成为了6)
2 ----> 3(我们在起始状态中给2重新编号成为了3)
5 ----> 5(我们在起始状态中给5重新编号成为了5)
1 ----> 8(我们在起始状态中给1重新编号成为了8)
6 ----> 2(我们在起始状态中给6重新编号成为了2)
那么终点状态也需要被一起映射,因为我们要让转换前的状态等于转换后的状态
那么我们只是重新给它们编号,所有转换前的状态等于转换后的状态
那么只需要再使用康托展开来把空间优化一下就可以了
代码实现
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <queue>
#include <set>
#include<unordered_map>
#define sc scanf
#define pr printf
#define Maxn 362880+5//1-9位置最多有9!种情况
using namespace std;
static const int FAC[] = { 1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880 }; // 阶乘,用来计算康托值
struct p {
char s[10];//用来存放魔板的状态
int hash;//魔板状态对应的hash值
};
p q[Maxn];//队列种最多不会有超过Maxn种状态
bool v[Maxn];//标记有哪些状态是可以到达的,但在这道题用来去重
string path[Maxn];//用来存放从起点到目标状态的路径(操作顺序),因为bfs是按照字典序操作的所以第一次到达一个状态就是最小的字典序
int l = 0;//队列头
int r = 0;//队列尾
char s[10];//用来存放起点的状态
char e[10];//用来存放终点的状态
//康托展开
int cantor(char* a)
{
int x = 0;
for (int i = 0; i < 8; ++i)
{
int smaller = 0; // 在当前位之后小于其的个数
for (int j = i + 1; j < 8; ++j)
{
if (a[j] < a[i])
smaller++;
}
x += FAC[9 - i - 1] * smaller; // 康托展开累加
}
return x + 1; // 康托展开值
}
//返回进行a操作后的状态信息
p a(p s)
{
for (int i = 0; i < 4; i++) {//反转字符串就可以了
swap(s.s[i], s.s[7 - i]);
}
s.hash = cantor(s.s);//更新康托值
return s;
}
//返回进行b操作后的状态信息
p b(p s)
{
p ans = { 0 };
//观察一下就可以得到
ans.s[0] = s.s[3];
ans.s[1] = s.s[0];
ans.s[2] = s.s[1];
ans.s[3] = s.s[2];
ans.s[4] = s.s[5];
ans.s[5] = s.s[6];
ans.s[6] = s.s[7];
ans.s[7] = s.s[4];
ans.hash = cantor(ans.s);//更新康托值
return ans;
}
//返回进行b操作后的状态信息
p c(p s)
{
p ans = { 0 };
//直接看样例就可以得到
ans.s[0] = s.s[0];
ans.s[1] = s.s[6];
ans.s[2] = s.s[1];
ans.s[3] = s.s[3];
ans.s[4] = s.s[4];
ans.s[5] = s.s[2];
ans.s[6] = s.s[5];
ans.s[7] = s.s[7];
ans.hash = cantor(ans.s);//更新康托值
return ans;
}
void bfs()
{
p s = { "12345678", 1 };//起点的状态和它对应的康托值
q[r++] = s;//入队
v[1] = 1;//把起点状态标记为访问过了
path[1] = "";//起点不需要任何操作就可以得到
while (l < r) {
s = q[l++];//出队
for (int i = 0; i < 3; i++) {//枚举三种操作
p t = s;//用来展示存放
switch (i) {
case 0: {
t = a(t);//进行a操作
if (!v[t.hash]) {//如果没有到达过
v[t.hash] = 1;//标记已到达
path[t.hash] = path[s.hash] + "A";//之前的路径加当前的操作即A
q[r++] = t;//入队
}
break;
}
case 1: {
t = b(t);
if (!v[t.hash]) {//如果没有到达过
v[t.hash] = 1;//标记已到达
path[t.hash] = path[s.hash] + "B";//之前的路径加当前的操作即B
q[r++] = t;//入队
}
break;
}
case 2: {
t = c(t);
if (!v[t.hash]) {//如果没有到达过
v[t.hash] = 1;//标记已到达
path[t.hash] = path[s.hash] + "C";//之前的路径加当前的操作即C
q[r++] = t;//入队
}
break;
}
}
}
}
}
int main() {
bfs();//得到固定起点的所有可以走到的情况
while (~sc("%s%s", s, e)) {//读入起点和终点的状态
char n[256] = "";//用来存放把起始的状态编号成了什么数字
for (int i = 0; i < 8; i++) {//遍历起点的状态的,给它们重新编号
n[s[i]] = i + 1;//存放对应的编号
s[i] = i + 1;//进行编号,从1开始
}
for (int i = 0; i < 8; i++) {
e[i] = n[e[i]];//把终点状态转换
}
int f = cantor(e);//计算转换后的康托值
cout << path[f] << endl;//打印路径
}
return 0;
}
速算24点
解题思路
- 每次从数组拿两个数进行计算再放入数组直到数组中只有一个数,再判断这个数是不是24就可以了
- 在做除法操作的时候还需要判断能不能被整除,和被除数不能为0
- 这种从数组中选择数的时候就隐含了一个括号,所以我们不需要枚举括号的位置
代码实现
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <queue>
#include <set>
#include<unordered_map>
#define sc scanf
#define pr printf
using namespace std;
unordered_map<string, int> a;//用来存放数值,把读入的字符串变成对应的数值
string q[4];//读入四个字符串
bool ans;//表示能不能组成24
//一共两个数,四种操作符,但一共有6中不同的组合
int ca(int op, int a, int b) {//op表示什么操作
if (op == 0 && a && b % a == 0)//被除数不能为0,并且要能够整除因为题目要求不能有小数
return b / a;
else if (op == 1)
return a - b;
else if (op == 2)
return b - a;
else if (op == 3)
return a * b;//a*b和b*a是一样的
else if (op == 4 && b && a % b == 0)//被除数不能为0,并且要能够整除因为题目要求不能有小数
return a / b;
return a + b;//a+b和b + a是一样的
}
void dfs(int n[], int size) {
if (ans) {//如果已经找到一种方法可以得到24,那么便不在往下搜索
return;
}
if (size == 1 && n[0] == 24) {//如果数组中只有一个数并且这个数是24代表找到了一个方案可以得到24
ans = true;//标记为找到了
return ;
}
//从数组中选两个数
for (int i = 0; i < size - 1; i++) {
for (int j = i + 1; j < size; j++) {
int num[20] = { 0 };//新的数组,即把两个数放入组合成一个数在放入数组的数组
int nSize = 0;//新数组的大小
for (int k = 0; k < size; k++) {//把没有选中的数放入新数组
if (k != i && k != j) {
num[nSize++] = n[k];
}
}
for (int k = 0; k < 6; k++) {//遍历6中操作
num[nSize++] = ca(k, n[i], n[j]);//新的值放入数组
dfs(num, nSize);//进行搜索
num[--nSize] = 0;//回溯
}
}
}
}
void solve(int n[])
{
ans = false;//每次初始化为false表示不能组成24
dfs(n, 4);//dfs搜索所有的情况
if (ans) {//如果ans==true代表找到了,打印Yes
pr("Yes\n");
}
else {
pr("No\n");
}
}
int main() {
a["A"] = 1;
a["2"] = 2;
a["3"] = 3;
a["4"] = 4;
a["5"] = 5;
a["6"] = 6;
a["7"] = 7;
a["8"] = 8;
a["9"] = 9;
a["10"] = 10;
a["J"] = 11;
a["Q"] = 12;
a["K"] = 13;
while (cin >> q[0] >> q[1] >> q[2] >> q[3]) {
int n[4] = { 0 };
if (q[0].size() + q[1].size() + q[2].size() + q[3].size()) {//只要所有字符串大小不都是0
//获取对应的值
n[0] = a[q[0]];
n[1] = a[q[1]];
n[2] = a[q[2]];
n[3] = a[q[3]];
solve(n);//把得到的数值数组进行传递
}
else {//表示输入结束
break;
}
}
return 0;
}