2024年3月29號題解

lwj1239發表於2024-03-30

魔板

解題思路

  1. 如果我們可以固定一個起點,那麼我們把所有的走法都列舉了,那麼就得到了可以走到的所有解
  2. 但題目給的起點並不是固定的,那麼有沒有一種函式(方法)可以把任意起點位置轉換成一個固定的起點,然後終點也可以轉換的呢
  3. 也就是說把一個起點和終點變成固定的其中的對應的終點

例如:

初狀態: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點

解題思路

  1. 每次從陣列拿兩個數進行計算再放入陣列直到陣列中只有一個數,再判斷這個數是不是24就可以了
  2. 在做除法操作的時候還需要判斷能不能被整除,和被除數不能為0
  3. 這種從陣列中選擇數的時候就隱含了一個括號,所以我們不需要列舉括號的位置

程式碼實現

#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;
}