My final codes for every OJ test.
时间限制: 1000 ms 内存限制: 2048 KB
给定二维平面上的一个圆和一个三角形,视两者为两个点集C和T。C和T均不包含图形的内部,仅包含图形的边界。判断集合C和T的交集是否非空。
(第一行)正整数N,代表接下来有N组测试数据。
(接下来的N行,每一行格式是:)r x0 y0 x1 y1 x2 y2 x3 y3 (解释:r为圆半径,(x0, y0)为圆心坐标,(x1,y1), (x2,y2), (x3,y3)为三角形3个顶点坐标)
0<N<1,000,000;横、纵坐标属于区间[-1000, 1000]
一共N行,每行输出数字0或1。若C和T交集非空,输出1;若C和T交集为空集,输出0.
2
1 0 0 5 5 6 6 7 4
1 0 0 0 2 1 -1 2 1
0
1
使用double数据类型可能更精确。
使用<stdio.h>中的scanf,printf。它们比中的cin, cout更快。
时间限制: 2000 ms 内存限制: 2048 KB
一般链表中,每个结点会有一个Next指针指向下一个结点,而复杂链表则还有一个Random指针指向链表中的任意一个结点或者NULL。一个复杂链表结点的C++定义如下:
struct ComplexNode
{
int Value;
ComplexNode* Next;
ComplexNode* Random;
};
本次实验需要同学们实现复杂链表的构建、删除、插入、访问等操作,直面C语言中最困难的指针,希望同学们能够勇敢面对本次实验,相信大家经过本次实验后都能够熟练运用指针。
题目的输入总共有5行,同学们需要构建一个包含N个结点的头结点不为空的复杂链表并完成相应操作,我们定义从头结点到尾结点依次为第0个结点到第N-1个结点。
第一行包含4个整数。分别为N(链表结点个数), M(需要删除的结点个数), K(需要插入的结点个数)和目标结点的索引T(与输出有关,-1<T<N-M+K,T为整数)。
第二行包含N个整数。依次为第0个结点到第N-1结点的值。
第三行包含N个整数,依次为第0个结点到第N-1个结点的Random指针指向结点的索引。注意,若第X个整数为-1则说明第X-1个结点的Random指针指向NULL。
第四行包含M个整数,依次为需要删除的结点在当前链表中的索引。注意,所有指向这些被删除结点的指针在结点删除后都应该指向NULL。每删除一个结点,则更新一次链表。
第五行包含3K个整数,三个数为一组。每一组中的第一个整数为待插入结点的值,第二个整数为待插入结点在当前链表中插入后的索引,第三个整数为待插入结点在当前链表中插入后Random指向结点的索引(第三个整数为-1代表待插入结点的Random指针指向NULL)。每一个结点插入当前链表后,更新当前链表。
每一个结点的值在区间[-20,20]内,0<N<20000,0<M<1000,0<K<1000
输出包含两行。
第一行:从目标结点T开始,通过Next指针访问链表,依次输出各结点的值,直到某一结点Next指针指向NULL,则输出-1结尾。
第二行:从目标结点T开始,通过Random指针访问链表,依次输出各结点的值,直到某一结点Random指针指向NULL,则输出-1结尾。
5 2 2 0
3 1 6 9 15
2 4 -1 1 -1
2 3
1 0 2 10 3 -1
1 3 1 10 9 -1
1 1 -1
使用<stdio.h>中的scanf,printf。它们比中的cin, cout更快。
题目的输入会保证对特定结点从Random指针进行访问时,不会出现“死锁”,即输出的序列长度一定是有限的。
输入和输出每一行中的各元素均用空格隔开。
每删除或插入一个新的结点后,链表都需要进行更新!
M不大于N。
时间限制: 1000 ms 内存限制: 2000 KB
N(N为偶数)个高矮不同的人排成两排照相,要求每一排都是从矮到高排列,而第二排每个人比第一排对应的人要高,列出所有可能的结果。
第一行正整数N(0<N<100)
第二行第一个人的身高h1
第三行第二个人的身高h2
…
第N+1行第N个人的身高hN
1.每种情况占据一行;
2.将第一排按照排队顺序输出身高,然后将第二排按照排队顺序输出身高;
3.身高之间加空格(第二排最后一人后面为换行符,无空格),两排之间不加换行符(第一排最后一人与第二排第一人之间为空格)。
4.最后统计所有情况数目并输出。
4
161
162
164
163
161 162 163 164
161 163 162 164
2
或者下列输出也正确(即不同情况之间可交换顺序):
161 163 162 164
161 162 163 164
2
1.使用<stdio.h>中的scanf,printf。它们比中的cin, cout更快。
2.每种情况输出完成后要换行。
3.在输出时,不同情况的输出先后顺序不影响答案的正确性。
时间限制: 1000 ms 内存限制: 80000 KB
随着科技的发展,通信的手段日新月异。然而不得不说,至今都没有比鸿雁传书更为深情浪漫的通信方式出现。
两个迷恋地球文明&中华文化的【三体人】出于浪漫主义需求,决定以后通过鸿雁传书的方式来进行通信。为了方便起见,我们姑且叫他们大刘和大白。鸿雁传书,情真意切,大刘和大白的感情也一日比一日深厚。
Fig.1 图文无关
两人的私密通信,自然是“不足为外人道”的。为此大刘煞费苦心地设计了一套用01比特来表示每个三体文字的方式,称为“鸿雁传书字典”。其巧妙之处在于,任何一个01比特串都不会是其他串的前缀串。于是收到一大堆01比特的大白对照着“鸿雁传书字典”,就能原样知道大刘想写的是什么三体文字了。而别人手中没有这个字典,自然不知道这些比特的含义。一个非常简单的(只包含四个可选三体文字)例子Fig.2所示。
Fig.2 合法的“鸿雁传书字典”及其传书过程示例
好景不长,由于三体文字实在是博大精深,有成千上万个。原本的“鸿雁传书字典”是大刘随手设计的,每个三体字都要用好多好多比特来描述。大刘有一次问大白“你吃了没”竟然用了23333个比特,写了一大叠纸,鸿雁送到一半累得吐血掉进黑暗森林再没飞起来。
Fig.3 累得掉进黑暗森林的鸿雁(注意这是三体的鸿雁,长得像鸽子是可以理解的)
大白得知后伤心地哭了很久(●—●),大刘也十分难过。他和大白保证会设计出新的、最好的“鸿雁传书字典”,为此他把自己和大白的所有传书都搜集了起来,并统计出了其中每一个三体文字的出现次数。
做完这一切后,大刘对着这个统计表犯了难,该怎么设计出一个最好的“鸿雁传书字典”,使得平均意义上表示一个三体文字需要的比特数最少呢?你能帮帮大刘么?
[注:文中图片来自互联网,侵权请联系17888833517撤回,谢谢~]
输入共有 N+1 行。
第 1 行包含一个整数N,代表大刘统计出的不同三体文字的数量。
第 2 行到第 N+1 行每行有一个整数,代表大刘统计的所有书信里,这行对应的三体文字出现的总次数。
- 大刘为了请你帮忙,已经把所有三体字按照【1体】-【2体】-【3体】- …… -【N体】的顺序进行了排列,也即第 k 行代表 “【k-1体】” 这个字出现的总次数。
- N不超过300,000。
输出共有 N+1 行。
第 1 行包含一个浮点数F,代表你设计的最优“鸿雁传书字典”中,表示每个三体字需要的平均比特数。保留6位小数。
第 2 行到第 N+1 行每行有一个“01”比特串,代表你设计的最优“鸿雁传书字典”中,表示这行对应三体字的01比特编码。
- 输出“鸿雁传书字典”中编码方案的时候,必须也按照【1体】-【2体】-【3体】- …… -【N体】的顺序进行输出,也即和输入文件中的顺序匹配。
- 表示“鸿雁传书字典”中的编码方案必须使用“0”和“1”来表示,例如“0101”。不接受别的等价描述,例如“2323”。
- 严格保证输出是N+1行,并且每行中不能有和编码比特无关的空格或其他字符。
5
2
1
2
2
3
2.300000
111
110
01
00
10
- 显然最优编码方案是不唯一的,大家只需要提供随意一种最优编码方案即可。
- 请大家务必看清输出格式的所有要求。
- 如果用到排序算法,请查资料学习编写快速排序的代码(冒泡排序可能无法过所有数据)。
时间限制: 2000 ms 内存限制: 6000 KB
社交网络可以用一个图来描述。假定有N个使用者,每个使用者可以用一个节点表示,如果使用者m和使用者n已经是线上朋友,表示两个节点已经连接,即图中包含连接m和n的边。在这个练习中,我们将为使用者推荐可能的朋友。
给定表示一个社交网络的图,包含N个节点和M条边。如果节点m和n不直接相连,但与K个以上(包括K个)的相同节点连接,我们可以认为他们可能认识,请给出指定用户的潜在好友(暂时还未直接连接的节点)。对于给定用户,请将其所有潜在好友按照他们共同好友数量由多到少排列输出,对于共同好友数量相同的情况,请按照序号从小到大排列输出。
输入的第一行是三个整数N,K,U。其中N表示社交网络中用户的个数,即网络节点的个数。K表示共同好友数不少于K的两个用户才互相算作潜在好友。U表示需要输出标号为U的用户的潜在好友,对应矩阵行/列标号为U的节点,U从0开始计。
输入的第二行到第N+1行是用户之间的图的邻接矩阵:0代表不是好友,1代表是好友。
请在一行中,输出用户U所有可能认识的人(共同好友数量不少于K并且目前还不是好友的人)的编号(即对应的矩阵的行列序号,从0开始计数),序号之间用空格隔开。按照共同好友数量由多到少排列输出,对于共同好友数量相同的潜在朋友,请按照序号从小到大排列输出。
6 1 3
0 1 0 1 0 1
1 0 0 0 0 1
0 0 0 0 0 1
1 0 0 0 0 1
0 0 0 0 0 1
1 1 1 1 1 0
1 2 4
图的邻接矩阵对角线元素没有意义,且为对称阵。
时间限制: 4000 ms 内存限制: 20000 KB
杨氏矩阵(Young Tableau)是对组合表示理论和舒伯特演算很有用的工具,它提供了一种方便的方式来描述对称和一般线性群的群表示,并研究它们的性质。本次任务中我们将对杨氏矩阵的基本操作进行一些探索。
杨氏矩阵一般指一个m×n 的二维数组,它的每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。需要注意的是,在这个矩阵中允许一些元素不存在的情况发生,也可以认为这些元素被设置成为无穷大(∞)。
可以容易发现,对于一组特定数据,杨氏矩阵的形式并不唯一。
在本次实验中我们需要完成杨氏矩阵的创建、查找、增加、删除操作。
输入一共有五行
第一行包含四个正整数,分别为N N1 N2 N3
第二行包含N个整数,储存了杨氏矩阵的内容,矩阵元素均为大于零不重复的正整数,-1代表矩阵内部换行,且该行最后一个数为-1,注意N并不等于矩阵元素的个数
第三行包含N1个正整数,需要同学们查找这些元素在杨氏矩阵中的位置
第四行包含N2个正整数,每一个代表需要插入到当前杨氏矩阵中的元素,不会给出重复的元素。
第五行包含N3个正整数,每一个代表需要删除的元素,不会给出当前矩阵中不存在的元素。
矩阵中元素的值的值位于区间[1,2147483647]内。
0<N<1000000,0<N1<N , 0<N2<50000 , 0<N3<50000
输出包含三行。
第一行给出查找结果:依次给出各个元素的坐标,如果元素不存在则认为坐标是(-1,-1)
第二行给出插入操作完成后的杨氏矩阵:分别输出每一行的所有元素值,输出-1代表换行,最后一个输出必须是-1。结果不唯一,你需要保证:1、结果是一个杨氏矩阵。2、矩阵中包含当前存在的所有元素
第三行给出删除操作完成后的杨氏矩阵:分别输出每一行的所有元素值,输出-1代表换行,最后一个输出必须是-1。结果不唯一,你需要保证:1、结果是一个杨氏矩阵。2、矩阵中包含剩余的所有元素
11 2 2 2
2 5 6 -1 4 10 -1 7 -1 15 -1
5 13
8 12
10 7
0 1 -1 -1
2 5 6 8 -1 4 10 12 -1 7 -1 15 -1
2 5 6 8 -1 4 12 -1 15 -1
输入和输出每一行中的各元素均用空格隔开
每插入或者删除一个元素后,后续操作均在新生成的矩阵上进行。
注:本实验中增加、删除后的矩阵结果有多种可能,只要同学们输出一个元素值符合要求的杨氏矩阵即可
时间限制: 1000 ms 内存限制: 1600 KB
三对角矩阵是指除主对角线和其相邻的上下两条对角线之外,其他所有元素都为0的矩阵。
本次实验给定一个三对角矩阵A和右端矩阵B,求解矩阵方程AX=B。
可以认为B=[b1,b2,...,bn]为一些列向量的组合,从而解出的矩阵X=[x1,x2,...,xn]为一些解向量的组合。
本次实验给出的矩阵A均为三对角非奇异方阵,解的准确程度由残差值 ||Axi - bi||2给出,这里i从1到n,要求对所有i,残差值都小于 0.1。
首先第一行输入两个正整数m和n,表示三对角矩阵A为m乘m的矩阵,右端矩阵B为m乘n阶矩阵,因此X也是m乘n矩阵。
接下来第二行输入m个浮点数,从左上到右下依次表示主对角线上的m个元素的值。
第三行输入m-1个浮点数,从左上到右下依次表示主对角线上方的相邻对角线的m-1个元素的值。
第四行输入m-1个浮点数,从左上到右下依次表示主对角线下方的相邻对角线的m-1个元素的值。
接下来的第5行到第n+4行,一共输入n行,每行m个浮点数,依次表示B矩阵从第1列到第n列的m个元素的值。
输出一共为n行,每行m个浮点数,依次表示X矩阵从第1列到第n列的m个元素的值。
根据以上定义,输出的每一行必须满足对应的残差值小于0.1。
5 2 \表示A为5乘5的矩阵,B为5乘2的矩阵
1.0 2.0 3.0 4.0 5.0 \表示主对角线上的五个数
1.0 1.0 1.0 1.0 \表示主对角线上方对角线的四个数
6.0 5.0 4.0 3.0 \表示主对角线下方对角线的四个数
3.0 13.0 23.0 33.0 37.0 \表示B的第一列的值
2.0 9.0 9.0 9.0 8.0 \表示B的第二列的值
1.0 2.0 3.0 4.0 5.0 \表示X矩阵第一列的值
1.0 1.0 1.0 1.0 1.0 \表示X矩阵第二列的值
输出结果并不唯一,只需要使X矩阵的所有列都满足残差小于0.1即可。
C++默认的输出精度可能不满足残差要求,同学们需要显式提高输出精度。
时间限制: 1000 ms 内存限制: 1560 KB
在工程实际问题中,某些变量之间的关系是存在的,但通常不能用式子表示,只能由实验或观测得到z=f(x,y)在一系列离散点上的函数值fi。希望通过这些数据(xi, yi, fi)计算函数z=f(x,y)在其他指定点处的近似值或获取其他信息。
本次实验的目标是:对于二维空间[(0, 0),(0, 1),(1, 0),(1, 1)]上的未知二元多项式函数,函数阶数较低,给出区间中的一部分点的值,请自己选择插值方法对区间内的函数进行估计,并计算待求点上的值,并与真值比较,误差应在一定限度内。
题目的输入共有N+2行,N为给定区间内点的数量
第一行包含一个整数,为N(N为整数)。
第二行到第N+1行每行包含三个数,为别为xi, yi, f(xi, yi)。
第N+2行包含两个数,为别为待求点的xt, yt。
N不超过4。
输出包含一行,为待求点的函数值的估计值f(xt, yt)输出包含一行,为待求点的函数值的估计值f(xt, yt)
4
0.2 0.2 0.1
0.2 0.7 0.4
0.7 0.2 0.2
0.7 0.7 0.6
0.5 0.5
0.376
可使用双线性插值等线性插值方法。
注意输出精度问题,可能需要使用特殊的算法保证精度,部分样例要求误差在1e-10以内。
不需要使用复数。
本次实验对算法时间、空间复杂度要求不高,可以考虑优先保证正确性。
时间限制: 2000 ms 内存限制: 3000 KB
小明是个土豪,家里有一大块土地,为了方便规划土地被划分成了N块宽度相同但是长度不一的矩形,记共同宽度为1作为本题中的长度计量单位,这些矩形具有统一的起点和朝向而且紧挨在一起,小明的土地具体情况如图所示
每块土地的长度从左到右记为h1,h2,...,hN,现在小明需要在自家的土地上新建一个运动场和一个游泳池,为了修建的方便运动场和游泳池只能建成矩形,而且矩形的起点和朝向与规划中划分的小矩形一致,另外为了打球时球不会落入游泳池,还需满足运动场和游泳池之间的距离大于等于K(由于起点和朝向相同,距离定义为两个矩形平行于朝向的边之间的最短距离),现在需要设计一个最佳的建设方案使得两个场地的面积之和最大.设计对于输入示例的设计方案示例如图
注意:K,h1,h2,...,hN均为正整数
第一行为N(正整数大于2小于等于11000) , K(正整数不超过N的一半)
第二行为各矩形的长度h1,h2,...,hN (正整数不超过1000)
一个正整数,游泳池和体育场的面积之和能达到的最大值
6 2
5 4 7 1 3 6
18
时间限制: 1000 ms 内存限制: 10240 KB
二阶魔方是 2x2x2 的立方体结构魔方,它共有 8 块,如下图所示:
我们可以定义魔方作为一个正六面体的六个面为 F(ront), B(ack), U(p), D(own), L(eft), R(ight)。我们根据先前后后,先上后下,先左后右的方法,依次给魔方的每一个方块编号,块编号的方式如下表所示:
带块编号的魔方平面展开图如下图所示:
与三阶魔方不同,二阶魔方的每个块均有 3 个面露在外面,并被涂为不同的颜色,共 6 种颜色。我们定义整个魔方作为魔方共有 24 个面。任意两个面,如果颜色不同,那么显然是不同的两个面;若颜色相同,但与其在同一个块上的另外两种颜色不可能全相同,那么这两个面也是不同的两个面。因此,二阶魔方的 24 个面各不相同。所以我们可以给每个面一个编号( 0 到 23 ),面编号的方式如下表所示(其中“ 0F ”表示“第 0 块;前面”):
带面编号的魔方平面展开图如下图所示:
根据这种面编号的方法,一个复原后的魔方可以写成一个长度为24的数组: [0, 1, 2, 3, …, 23] ,一个被打乱的魔方可以写成这个数组的重排,例如下面的这种情况:
此时用数组表示这个状态为: [10, 11, 9, 5, 3, 4, 6, 7, 8, 22, 23, 21, 1, 2, 0, 14, 12, 13, 18, 19, 20, 17, 15, 16] 。
我们定义魔方的状态是不受视角影响的,也就是说只有拧动魔方后,其状态才会改变。这里我们可以分析一下二阶魔方存在多少种不同的状态。由于视角的不同,二阶魔方的每一种状态会对应到 24 种不同的如图 2 所示的平面展开图(6 个面均可作为正面,同时又可以以正面为轴滚动 4 次,得到 6x4=24 种不同的平面展开图)。二阶魔方 8 个块的位置均可任意互换(8! 种情况)。如果固定一个块作为参考,那么另外 7 个块中每个都可以有 3 种不同的朝向(37 种情况)。于是我们得到了 8!37 种不同的平面展开图。所以二阶魔方的状态总数为:
在还原二阶魔方的过程中,“FRU注释”是一种比较通用的还原步骤注释。FRU注释只旋转魔方的 正面(F: front)、右面(R: right)和 上面(U: up),并且可以分别 顺时针旋转90°(用+表示)、180°(用2表示)和 逆时针旋转90°(用-表示)。这样魔方每步就有 9 种的变化方式(注意到在可以调整视角的情况下其他的旋转方式是等价于这 9 种方式中的某一种的)。下图描述了FRU注释的操作过程:
若采用FRU注释的 9 种方式旋转魔方,对于二阶魔方的任意一种状态,最坏情况下只需要 11 步就可以将魔方还原。其中,在 3674160 种不同的魔方状态中,有一半左右(1887748 种)的状态最少需要 9 步才能还原魔方。下表给出了最少旋转次数和对应的状态总数的关系:
我们的问题就是:给出一个被打乱的二阶魔方(根据前文的方法表示成打乱顺序的数组 [0, 1, 2, 3, …, 23] ),求出还原魔方的最少步数(每步只能是FRU注释的 9 种操作的一种),并且按照 FRU注释 给出每步的具体操作以及每步操作后的结果(长度为 24 的数组)。
输入为某种被打乱的魔方的状态,输入的格式为打乱顺序的数组 [0, 1, 2, 3, …, 23] 。
注意到FRU注释的9种旋转方式均不会改变魔方中一块的状态(后下左),所以还原后的魔方的状态由这个块的初始状态唯一确定。本题中为了使得还原后的魔方状态用数组表示为 [0, 1, 2, 3, …, 23] ,会保证所有测试数据的数组的 18, 19, 20 均在原本的位置。
首先第一行输出一个正整数 n ,表示还原魔方所需的 最少 步数。
接下来的输出有 2n 行(若 n = 0 则无需输出)。第 (2i – 1) 行输出一个字符串,表示还原魔方的每步操作,字符串要求是 9 种旋转操作(F+,F2,F-,R+,R2,R-,U+,U2,U-)中的一种。第 (2i) 行输出一个长度为 24 的数组,这个数组表示经过第 (2i – 1) 行的操作后魔方的状态。
注意:使用最少步数还原魔方可以有多种操作方式,请输出任意一种即可。前 4 组测试数据保证最少步数不超过 7 步,后 6 组的测试数据无限制。
10 11 9 5 3 4 6 7 8 22 23 21 1 2 0 14 12 13 18 19 20 17 15 16
2
U-
0 1 2 11 9 10 6 7 8 22 23 21 12 13 14 4 5 3 18 19 20 17 15 16
R-
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
本题可能需要了解“状态空间搜索”、“状态压缩”和“双向广度优先搜索”。这部分内容可以参考百度百科的搜索算法。
1.状态空间搜索
状态空间搜索就是将问题的求解过程转化为从初始状态到目标状态寻找路径的过程。状态空间可以看成一个图,图上的点就是某个 状态 ,边则是某个状态到另一个状态的 状态转移 关系。使用状态空间搜索求解问题的关键就是:利用有效的数据结构对状态进行存储,实现状态间的转移关系,进而使用 深度优先搜索 或 广度优先搜索 算法计算初始状态到目标状态路径。
本题中,状态就是魔方的状态,即长度为 24 的数组,状态转移关系则是FRU注释中的 9 种操作。
所以,我们需要把FRU注释中的 9 中操作对应的颜色变换列举出来(即变换后的面的编号对应原来的哪个编号),然后依据输入给的初始状态,通过广度优先搜索遍历可能转移到的状态,直到搜索到最终状态( [0, 1, 2, 3, …, 23] ),便求出了还原魔方的最少步数,并且可以通过回溯得到每步的操作。
2.状态压缩
在搜索的过程中,我们需要标记哪些状态已经被访问过了。对于简单的搜索,我们可以直接用 bool 数组来标记状态访问与否。但是,本题的状态是一个长度为 24 的数组,我们需要建立哈希表来存储。为了简化程序,这里建议大家直接使用 C++ STL 中的 map 或 unordered_map 来实现哈希表,并且状态的表示也用 STL 中的 vector 而不是数组(因为 vector 可以直接用在 map 中,而数组不行)。不过,用数组或 vector 表示一个状态会占用较多的内存,并且会延长哈希表定位键值的时间。
状态压缩要考虑的问题是如何用 更有效的方式对状态进行编码。对本题而言,注意到只有 6 种颜色,所以可以先将颜色编号( 0 ~ 5 ),进而使用 六进制 对长度为 24 的状态数组编码:六进制的第 i 位,表示第 i 个位置的颜色。所以我们需要一个取值范围在 0 ~ 624-1 的数字来表示一个状态,这个取值范围恰好在无符号 8 字节整数(C++中的 unsigned long long)的取值范围(0 ~ 264-1)内。于是,我们在使用 map 标记状态时,就不需要使用 vector 作为键值,而是使用这个 unsigned long long 的编码值了。比如最初状态的数组使用六进制状态压缩的表示为:
而我们要获取第 i 个位置的颜色时,便可直接让压缩后的编码对 6i+1 取模后,再对 6i 取整即可。
也可以根据块编号以及块状态进行编码,8 块的其中一块(块编号为6的块)的位置及状态已经是确定的了,因此最多只有 7 个块的顺序( 7! )及7个块的状态( 3^7 )。实际上确定了剩余 7 个块中 6 个块的状态,最后一个块的状态就已经确定了,因此总状态数为 7!*3^6 = 3674160,与前面的分析相同。所以可以使用14位的 unsigned long long 进行编码,如还原后的状态为:01234570000000(前 7 位表示块的位置,后 7 位表示每个块的状态( 0, 1, 2 )。
当然,以上两种压缩方法只是简单的例子,存在更好的压缩方式,因为总状态数仅有 3674160 种。
3.双向广度优先搜索
广度优先搜索的过程中需要维护一个存储待搜索状态的队列,因此广度优先搜索的问题就是会占用很大的内存。而对于目标状态确定的问题来说,双向广度优先搜索是减少内存开销的一个有效手段。双向广度优先搜索的思想就是 从 初始状态 和 目标状态 同时进行广度优先搜索,保持搜索的层数同步,最终两个方向的搜索所遍历的状态会在中间相遇,从而求出了最短的路径。这样一来便减去了很多不必要的搜索状态,因为搜索的过程中每层的状态数是近似成倍增加的。
使用双向广度优先搜索需要确定初始状态和目标状态。对于本题而言,初始状态是直接输入的,而目标状态即为 [0, 1, 2, 3, …, 23]。另外,对于本题而言,若前一步的操作为F(+, -, 2),则后一步的操作不会仍为F(+, -, 2),因为任意连续的两次以上F的操作可以用某种一次以下F的操作表示,U和R同理。依此编写代码也可以减少时间和空间的开销。
PS:建议尽可能精简代码,重复的代码封装成函数调用,充分利用数组,以减少出错的可能性。