魔板
解題思路
- 如果我們可以固定一個起點,那麼我們把所有的走法都列舉了,那麼就得到了可以走到的所有解
- 但題目給的起點並不是固定的,那麼有沒有一種函式(方法)可以把任意起點位置轉換成一個固定的起點,然後終點也可以轉換的呢
- 也就是說把一個起點和終點變成固定的其中的對應的終點
例如:
初狀態: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;
}