UVa 1343 The Rotation Game 旋转游戏 IDA* BFS 路径还原

news2024/11/18 14:42:50

题目链接:The Rotation Game
题目描述:

给定二十四个整数,这二十四个整数由八个一,八个二,八个三组成,从左到右,从上到下依次描述下图方格中的数字:
在这里插入图片描述
例如上图左边对应的输入就是 [ 1 , 1 , 1 , 1 , 3 , 2 , 3 , 2 , 3 , 1 , 3 , 2 , 2 , 3 , 1 , 2 , 2 , 2 , 3 , 1 , 2 , 1 , 3 , 3 ] [1, 1, 1, 1, 3, 2, 3, 2, 3, 1, 3, 2, 2, 3, 1, 2, 2, 2, 3, 1, 2, 1, 3, 3] [1,1,1,1,3,2,3,2,3,1,3,2,2,3,1,2,2,2,3,1,2,1,3,3]。你每一次可以进行 A − H A-H AH八种操作,上图给出了 A A A操作和 C C C操作如何进行,其他的操作如何进行可以类似的推出,你的任务是找到最少的操作步数让中间的八个数字为同一个数字,例如上图左边经过两次操作能将中间全部变成 2 2 2,如果有多个解,那么你需要输出字典序最小的解,同时你还需要输出中间的数字。

题解:

本题可以使用 I D A ∗ ( I t e r a t i v e D e e p e n i n g A S t a r ) IDA*(Iterative Deepening A Star) IDA(IterativeDeepeningAStar)算法,直接依次枚举进行的操作(需要注意的是,应该先枚举字典序小的),很明显可以发现,每一次移动最多只会让中间的位置正确的数字个数增加一,所以可以考虑的剪枝是:最大深度与当前深度之差如果小于中间最少的不对的数字时进行剪枝。
当然本题还有其他的做法。
本题也可以通过 B F S BFS BFS来做,不过使用 B F S BFS BFS来解决本题的时候,我们可以转换一下思路,我们可以把目标状态能够到达的所有其他状态枚举出来,这样的花费与从某个状态到目标状态是一样的。但是这样枚举存在一个问题:时间复杂度过高。
时间复杂度如何计算?要计算时间复杂度也就是计算所有可能的状态,也就是计算 24 24 24个数字,这 24 24 24个数字含有三种数字,每种数字的个数均为 8 8 8的排列个数,那么根据高中的组合数学知识,我们可以知道可能的排列个数为 24 ! 8 ! 8 ! 8 ! \frac {24!} {8!8!8!} 8!8!8!24!,这个数字是比较大的。不过我们有新的方法,因为只需要中间的八个数字相同,我们可以依次枚举中间可能的数字(这里是 1 , 2 , 3 1, 2, 3 1,2,3),不同于中间可能的数字记为 0 0 0,这样可能状态数量就变成了 24 24 24个数字包含两种数字,其中一种数字的个数是 8 8 8,另一个种数字的个数是 16 16 16的排列个数,那么此时的排列个数变成了 24 ! 8 ! 16 ! \frac {24!} {8!16!} 8!16!24!此时的复杂度就到了可以接受的范围了。我们在 B F S BFS BFS完成之后可以不用存储每一次的路径,而是在后续来还原出路径,这样可以节省一定的空间开销,还原的过程类似于 B F S BFS BFS的过程,只是还原的时候只会访问最佳的路径,而不是所有的路径。同时在 B F S BFS BFS的时候,由于在队列中放入一个数组非常不好处理,所以我们需要将状态进行编码,由于一共只有 24 24 24个状态,那么我们可以用 24 24 24位的二进制数来表示每一个位置的状态。
经过测试 I D A ∗ IDA* IDA速度要快得多。

IDA*代码:

#include <bits/stdc++.h>

using namespace std;

int g[24];
vector<char> ans;

// 数据的下标 6 7 8 11 12 15 16 17为中间的八个数字
// 返回最少需要几个数字让中间八个数字相同
int getWrongPos()
{
    int cnt[4] = {0};
    cnt[g[6]]++;
    cnt[g[7]]++;
    cnt[g[8]]++;
    cnt[g[11]]++;
    cnt[g[12]]++;
    cnt[g[15]]++;
    cnt[g[16]]++;
    cnt[g[17]]++;
    return 8 - max(cnt[1], max(cnt[2], cnt[3]));
}

void move(int op)
{
    int temp = 0;
    switch(op) {
        case 0:
            temp = g[0];
            g[0] = g[2];
            g[2] = g[6];
            g[6] = g[11];
            g[11] = g[15];
            g[15] = g[20];
            g[20] = g[22];
            g[22] = temp;
            break;
        case 1:
            temp = g[1];
            g[1] = g[3];
            g[3] = g[8];
            g[8] = g[12];
            g[12] = g[17];
            g[17] = g[21];
            g[21] = g[23];
            g[23] = temp;
            break;
        case 2:
            temp = g[10];
            for (int i = 10; i >= 5; i--) { g[i] = g[i - 1]; }
            g[4] = temp;
            break;
        case 3:
            temp = g[19];
            for (int i = 19; i >= 14; i--) { g[i] = g[i - 1]; }
            g[13] = temp;
            break;
        case 4:
            temp = g[23];
            g[23] = g[21];
            g[21] = g[17];
            g[17] = g[12];
            g[12] = g[8];
            g[8] = g[3];
            g[3] = g[1];
            g[1] = temp;
            break;
        case 5:
            temp = g[22];
            g[22] = g[20];
            g[20] = g[15];
            g[15] = g[11];
            g[11] = g[6];
            g[6] = g[2];
            g[2] = g[0];
            g[0] = temp;
            break;
        case 6:
            temp = g[13];
            for (int i = 13; i <= 18; i++) { g[i] = g[i + 1]; }
            g[19] = temp;
            break;
        case 7:
            temp = g[4];
            for (int i = 4; i <= 9; i++) { g[i] = g[i + 1]; }
            g[10] = temp;
            break;
        default:
            return;
    }
}

void undoMove(int op)
{
    if (op % 2 == 0) { move((op + 5) % 8); }
    else { move((op + 3) % 8); }
}

bool dfs(int nowDepth, int maxDepth)
{
    int wrongPos = getWrongPos();
    if (nowDepth == maxDepth) { return wrongPos == 0; }
    if (maxDepth - nowDepth < wrongPos) { return false; }
    for (int i = 0; i < 8; i++) { // 要求字典序最小只需要从小到大枚举即可
        ans.push_back(i + 'A');
        move(i);
        if (dfs(nowDepth + 1, maxDepth)) { return true; }
        undoMove(i);
        ans.pop_back();
    }
    return false;
}

int main()
{
    ios::sync_with_stdio(false);
    while (cin >> g[0] && g[0] != 0) {
        for (int i = 1; i < 24; i++) { cin >> g[i]; }
        ans.resize(0);
        for (int maxDepth = 0; ; maxDepth++) {
            if (dfs(0, maxDepth)) {
                if (maxDepth == 0) {
                    cout << "No moves needed" << endl << g[6] << endl;
                } else {
                    for (auto ch : ans) { cout << ch; }
                    cout << endl << g[6] << endl;
                }
                break;
            }
        }
    }
    return 0;
}

BFS代码:

#include <bits/stdc++.h>

const int INF = 0x3f3f3f3f;

using namespace std;

int g[24];
int finalStatus[] = {0, 0,
                     0, 0,
             0, 0, 1, 1, 1, 0, 0,
                   1,    1,
             0, 0, 1, 1, 1, 0, 0,
                     0, 0,
                     0, 0};
int finalStatusEncode, ansDis, ans;
map<int, int> dis;
string ansStr; // 这里改成string可以方便的比较大小

int encode(int *status, int number)
{
    int statusEncode = 0;
    for (int i = 0; i < 24; i++) { statusEncode |= (status[i] == number ? (1 << i) : 0); }
    return statusEncode;
}

void decode(int statusEncode, int *status)
{
    for (int i = 0; i < 24; i++) { status[i] = (statusEncode & (1 << i)) >> i; }
}

void move(int op, int *g); //该函数与IDA*中一样,这里不再给出

void bfs()
{
    finalStatusEncode = encode(finalStatus, 1);
    queue<int> q;
    q.push(finalStatusEncode);
    dis[finalStatusEncode] = 0;
    while(!q.empty()) {
        int nowStatus = q.front();
        q.pop();
        for (int i = 0; i < 8; i++) {
            decode(nowStatus, g); // 先解码,解码的目的是为了进行移动操作
            move(i, g);
            int newStatus = encode(g, 1);
            if (dis.count(newStatus) == 0) {
                dis[newStatus] = dis[nowStatus] + 1;
                q.push(newStatus);
            }
        }
    }
}

void getPath()
{
    int gTemp[24] = {0};
    for (int number = 1; number <= 3; number++) {
        int nowStatus = encode(g, number);
        if (ansDis == dis[nowStatus]) {
            string nowStr;
            while (nowStatus != finalStatusEncode) {
                for (int i = 0; i < 8; i++) {
                    decode(nowStatus, gTemp);
                    move(i, gTemp);
                    int newStatus = encode(gTemp, 1);
                    if (dis.count(newStatus) == 1 && dis[newStatus] + 1 == dis[nowStatus]) { // 在最短路径上
                        nowStatus = newStatus;
                        nowStr.push_back(i + 'A');
                        break;
                    }
                }
            }
            if (nowStr < ansStr) {
                ansStr = nowStr;
                ans = number;
            }
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    bfs();
    while (cin >> g[0] && g[0] != 0) {
        for (int i = 1; i < 24; i++) { cin >> g[i]; }
        ansDis = INF;
        ansStr = "Z"; // "Z"比所有可能的答案的字典序都要大
        for (int number = 1; number <= 3; number++) { // 分别枚举中间的数字
            int status = encode(g, number);
            ansDis = min(ansDis, dis[status]); // 题目一定是有解的
        }
        if (ansDis == 0) {
            cout << "No moves needed" << endl << g[6] << endl;
        } else {
            getPath();
            cout << ansStr << endl << ans << endl;
        }
    }
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/348614.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java中List排序的3种方法!

引用&#xff1a;https://blog.csdn.net/sinat_32849897/article/details/122098256 在某些特殊的场景下&#xff0c;我们需要在 Java 程序中对 List 集合进行排序操作。比如从第三方接口中获取所有用户的列表&#xff0c;但列表默认是以用户编号从小到大进行排序的&#xff0c…

acwing 2 普通背包 2维做法的坑

背包问题应该都挺熟了&#xff0c;但还是放一下题目 无论是一维还是二维的解法&#xff0c;思路都比较一致&#xff0c;就是用一个二维的dpdpdp矩阵&#xff0c;dp[i][j]dp[i][j]dp[i][j] 的定义为前 iii 个元素的最优组合在容量为 jjj 的背包的最大价值。 这个定义非常的巧妙…

前缀和-蓝桥杯

一、前缀和的概念数组a[0]~ a[n-1]&#xff0c;前缀和sum[i]等于a[0] ~ a[i]的和:sum[0] a[0]sum[1] a[0] a[1]sum[2] a[0] a[1] a[2] ......在O(n)时间内求所有前缀和: sum[i] sum[i-l] a[i]a[0]一般不用。二、前缀和与区间问题预计算出前缀和&#xff0c;能快速计算出区…

C语言结构体(初阶)声明、初始化、成员访问、传参

目录结构体类型的声明结构体变量的定义和初始化结构体成员访问结构体传参1.结构体类型的声明//1. struct Book {char name[20];int price; //成员列表 }b3,b4,b5;//全局变量 int main() {struct Book b1;//b1,b2局部变量struct Book b2;return 0; }//2. struct Book {char n…

重磅!ChatGPT席卷全球,Salesforce将推出EinsteinGPT!

2月9日&#xff0c;Salesforce首席执行官Marc Benioff在Twitter上发布了这样一则推文&#xff0c;表示将于3月7日的TrailblazerDX 23上发布Salesforce EinsteinGPT&#xff0c;随后Salesforce的官方Twitter也进行了转发。 虽然还没有正式的新闻稿&#xff0c;但不少业内人士猜测…

C++【模板初阶】

✨个人主页&#xff1a; Yohifo &#x1f389;所属专栏&#xff1a; C修行之路 &#x1f38a;每篇一句&#xff1a; 图片来源 No one saves us but ourselves, no one can and no one may. We ourselves must walk the path. 除了我们自己&#xff0c;没有人能拯救我们&#xf…

FreeRTOS-Tickless低功耗模式 | FreeRTOS十四

目录 说明&#xff1a; 一、低功耗模式简介 1.1、STM32低功耗模式 二、Tickless模式 2.1、Tickless模式如何功耗 2.2、Tickless模式设计思想 2.3、为了降低功耗&#xff0c;又不影响系统运行&#xff0c;怎么能做到呢&#xff1f; 三、Tickless模式修改配置 3.1、配置…

如何提升 ETF 期权隐含波动率和希腊值的计算速度?

期权的隐含波动率可以反应市场对未来的预期&#xff0c;通常使用牛顿法和二分法来计算。这两种方法都需要频繁迭代&#xff0c;且迭代次数不能确定&#xff0c;核心代码无法向量化&#xff0c;因此只能通过循环来逼近求解。这就导致在期权相关计算中&#xff0c;隐含波动率往往…

PO模式在Selenium中简单实践

初识PO模式 PO&#xff08;PageObject&#xff09;是一种设计模式。简单来说就是把一些繁琐的定位方法、元素操作方式等封装到类中&#xff0c;通过类与类之间的调用完成特定操作。 PO被认为是自动化测试项目开发实践的最佳设计模式之一。 在学习PO模式前&#xff0c;可以先…

InstructGPT笔记

一、InstructGPT是在GPT3上微调&#xff0c;ChatGPT是在GPT3.5上微调 二、该论文展示了怎么样对语言模型和人类意图之间进行匹配&#xff0c;方法是在人类的反馈上进行微调。 **三、方法简介&#xff1a;**收集很多问题&#xff0c;使用标注工具将问题的答案写出来&#xff0…

大数据面试题flume篇

1.Flume 的Source&#xff0c;Sink&#xff0c;Channel 的作用&#xff1f;你们Source 是什么类型&#xff1f; 1. 作用 &#xff08;1&#xff09;Source组件是专门用来收集数据的&#xff0c;可以处理各种类型、各种格式的日志数据&#xff0c;包括 avro、thrift、exec、jm…

Nginx介绍及安装(windows版,Linux版)

目录 一、Nginx介绍 1、Nginx优势 2、Nginx作用 3、部署静态资源 4、代理 5、负载均衡 二、Nginx安装步骤&#xff08;windows版&#xff09; 三、Nginx安装步骤&#xff08;Linux版&#xff09; 1、官网下载安装包&#xff0c;下载完之后上传到Linux系统上 2、在Lin…

股票量化策略是如何被执行出来的?

在股票量化方面&#xff0c;很多投资者是不知道怎么样挖掘量化策略&#xff0c;便在量化交易接口方面会通过股票交易接口将数据慢慢挖掘出来&#xff0c;就简单的通过api接口调用数据方面&#xff0c;直接通过交易接口端输出交易持仓数据&#xff0c;并且通过交易系统对数据的筛…

什么是“镜像浏览”?文件夹加密后的镜像浏览有什么用?

电脑中的文件夹经常用来储存各种重要文件&#xff0c;加密保护成为很多人的选择&#xff0c;而夏冰加密软件拥有各种适用于不同场景的文件夹加密软件&#xff0c;备受用户喜爱。在我们打开加密文件夹之后&#xff0c;我们可以在加密控制面板中发现“镜像浏览”的按钮&#xff0…

Sharding-jdbc

一、概念理解垂直切分&#xff1a;包含垂直分库和垂直分表1.1、垂直分库 &#xff1a;专库专用&#xff08;按照业务类型对表分类&#xff09;1.2、垂直分表&#xff1a;基于数据表的列&#xff08;字段&#xff09;为依据切分的&#xff0c;是一种大表拆小表的模式。1.3、垂直…

【Python--torch(激活函数说明+代码讲解)】激活函数(sigmoid/softmax/ELU/ReLU/LeakyReLU/Tanh)

【Python–torch】激活函数(sigmoid/softmax/ELU/ReLU/LeakyReLU/Tanh) 文章目录【Python--torch】激活函数(sigmoid/softmax/ELU/ReLU/LeakyReLU/Tanh)1. 介绍2. 常用激活函数说明2.1 Sigmoid2.1.1 公式2.1.2 图像2.1.3 代码解读2.2 Softmax2.2.1 公式2.2.2 代码解读2.3 ELU2.…

荧光探针Pyrene-PEG2-Propargyl,芘甲酰胺-二聚乙二醇-丙炔

Pyrene-PEG2-Propargyl物理参数&#xff1a; CAS号&#xff1a;N/A | 英文名&#xff1a;Pyrene-PEG2-Propargyl |中文名&#xff1a;芘甲酰胺-二聚乙二醇-丙炔分子式&#xff1a;C24H21NO3分子量&#xff1a;371.44纯度标准&#xff1a;95%外形颜色&#xff1a;淡黄色或白色固…

shell学习4

目录 一、统计文本中的词频 二、压缩javascript 三、打印文件的或行中的第n个单词或列---awk 3.1 利用awk打印文件中每行中的第五个单词。 3.2 利用awk打印当前目录下的文件的权限和文件名 3.3 利用awk打印从M行到N行这个范围内的所有文本 3.4 利用awk 部分提取文件中的内…

opencv复习

文章目录图像衡量结果&#xff08;损失函数&#xff09;预测的好坏前向传播 反向传播图像 实质是矩阵 长 宽 像素通道&#xff08;0-255 0 黑 255 亮&#xff09; 假设这里做一个10分类 行向量✖列向量是一个数 分类 最后的结果是一个各个分类的概率值 这里的b是偏置项&…

学校节能降耗减排方案——能耗监管平台的建设及效果剖析

摘要&#xff1a;作为崭新的校园能耗管理手段&#xff0c;能耗监测平台以传统管理方式无法企及的优势有力地提升了高校能源管理工作的水平&#xff0e;从而受到了相关管理者的青睐。本文梳理总结了高校能耗监测平台的基本组成和优势特点&#xff0c;同时对能耗平台建设和使用中…