点这里看题目。
分析做这道题目需要一点背景知识:
关于魔方群:
在这里我们研究的是二阶魔方,所以我们的范围也就仅是二阶魔方群。
由名字就可以知道,二阶魔方群应该是二阶魔方的所有可能状态构成的群。在此我们定义,两个状态是相等的,当且仅当可以通过旋转整个魔方使得两个状态在对应的色块上颜色一致。
为了避免无效重复,一种常用的处理方法是,我们可以固定魔方的一个角不转动,比如左上前的那个角,也就是 \((Y,O,B)\),固定不动。那么,有效的转动就是对右侧面、下侧面、后侧面的转动。
注意到一点:任意的转动都是对于魔方色块的重排,因此我们可以想到用置换表示魔方变换——自然也就可以用置换表示魔方的状态(但此时,置换相等就被定义为“对于一个还原的魔方操作后得到了同样的魔方状态”),因而二阶魔方群其实是 \(24\) 阶对称群的子群。
根据这样的表述,二阶魔方群的群元是置换而非单纯的状态,这样我们才可以方便地定义群上的运算、逆元和单位元。
我们可以尝试计算二阶魔方群的阶数。
由于二阶魔方只有角块,我们自然就考虑角块的状态。由于一个块固定,那么其余七个块排列方式为 \(7!\);而总共八个块,只要确定了七个块的状态,剩下的一个块只有一种情况才能使它能够还原(详见此知乎),所以该群的阶数为:
\[\frac{7!\times 3^7}{3}=3674160 \]
注意到每个群元可以用一个色块的置换来表示,因此可以方便地找出一个状态的逆。考虑当前状态 \(A\),由初始状态施加操作 \(p\) 达成,而状态 \(B\) 由初始状态施加操作 \(q\) 达成,那么从 \(A\) 到 \(B\) 相当于对 \(A\) 先施加 \(p^{-1}\),再施加 \(q\)。因此,我们可以找到 \(p^{-1}q\) 对应的状态 \(C\),而从初始状态到达 \(C\) 的最短步数就是答案。
从初始状态到达某个状态的最短步数可以用 BFS 预处理。而我们需要做的是:对于某个魔方状态,将它正规化,即修正它的左上前的角块;接着我们可以查表找出这个状态对应的置换;算出目标置换,接着查表找答案。
实现细节:
-
如果实现旋转:打出所需的操作对应的置换的表。比如,顺时针旋转右侧面可以记作 \(R\),那么我们的置换内记录的是 \(i\rightarrow R_i\)。而两个操作 \(f\) 和 \(g\) 复合的时候,我们希望得到的是 \(f_i\rightarrow f_{g_i}\),所以操作是 \(g\circ f\)。
-
如何压缩状态:由操作的置换转化成最终魔方的状态,再压缩魔方的状态。
一种暴力的方法是,将魔方的状态也看作是一个排列,康托展开后可以用
__int128
或者两个long long
存下来;更加优雅的方法是,根据阶数的计算方法,将角块的排列、六个块的朝向压缩到一个数内,理论上值应该为阶数个。
-
如何修正魔方:不停地旋转魔方,使得左上前的这个位置可以遍历到每一个角块,那么每次检查这个位置,这可以将我们所需的 \((Y,O,B)\) 移动到左上前的位置。此时 \((Y,O,B)\) 内部顺序可能还有误,那么按照体对角线旋转调整即可。
小结:
- 建议了解一些群论知识,了解一些常见的群;
- 写代码的时候,理清楚处理的对象,比如群元是置换而非魔方状态;
- 理清楚计算顺序,不要被直觉误导。虽然操作是按时间顺序“向前”,但是在置换上体现的是从右往左计算——与直觉并不相符;
#include <queue>
#include <cstdio>
#include <cstring>
#include <utility>
#include <iostream>
#include <algorithm>
#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )
typedef __int128 CantorIndex;
const int MAXN = 3.7e6 + 5, mod = 5e6 + 77;
template<typename _T>
void read( _T &x )
{
x = 0; char s = getchar(); int f = 1;
while( ! ( '0' <= s && s <= '9' ) ) { f = 1; if( s == '-' ) f = -1; s = getchar(); }
while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
x *= f;
}
template<typename _T>
void write( _T x )
{
if( x < 0 ) putchar( '-' ), x = -x;
if( 9 < x ) write( x / 10 );
putchar( x % 10 + '0' );
}
char color[] = { 'Y', 'O', 'B', 'R', 'G', 'W' };
int vertPerm[] = { 2, 0, 3, 1, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 4, 5, 6, 7, 21, 23, 20, 22 };
int thruPerm[] = { 6, 4, 7, 5, 22, 20, 23, 21, 10, 8, 11, 9, 2, 0, 3, 1, 17, 19, 16, 18, 14, 12, 15, 13 };
int cornPerm[] = { 9, 11, 8, 10, 3, 2, 1, 0, 5, 7, 4, 6, 20, 21, 22, 23, 14, 12, 15, 13, 17, 19, 16, 18 };
int righCloPerm[] = { 0, 9, 2, 11, 4, 5, 6, 7, 8, 21, 10, 23, 14, 12, 15, 13, 3, 17, 1, 19, 20, 18, 22, 16 };
int downCloPerm[] = { 0, 1, 2, 3, 4, 5, 10, 11, 8, 9, 14, 15, 12, 13, 18, 19, 16, 17, 6, 7, 21, 23, 20, 22 };
int backCloPerm[] = { 6, 4, 2, 3, 22, 5, 23, 7, 8, 9, 10, 11, 12, 0, 14, 1, 17, 19, 16, 18, 20, 21, 15, 13 };
int righCouPerm[] = { 0, 18, 2, 16, 4, 5, 6, 7, 8, 1, 10, 3, 13, 15, 12, 14, 23, 17, 21, 19, 20, 9, 22, 11 };
int downCouPerm[] = { 0, 1, 2, 3, 4, 5, 18, 19, 8, 9, 6, 7, 12, 13, 10, 11, 16, 17, 14, 15, 22, 20, 23, 21 };
int backCouPerm[] = { 13, 15, 2, 3, 1, 5, 0, 7, 8, 9, 10, 11, 12, 23, 14, 22, 18, 16, 19, 17, 20, 21, 4, 6 };
int pos[24][2] = { { 0, 2 }, { 0, 3 }, { 1, 2 }, { 1, 3 },
{ 2, 0 }, { 2, 1 }, { 3, 0 }, { 3, 1 },
{ 2, 2 }, { 2, 3 }, { 3, 2 }, { 3, 3 },
{ 2, 4 }, { 2, 5 }, { 3, 4 }, { 3, 5 },
{ 2, 6 }, { 2, 7 }, { 3, 6 }, { 3, 7 },
{ 4, 2 }, { 4, 3 }, { 5, 2 }, { 5, 3 }, };
char str[10][20];
struct Permutation
{
int p[24];
Permutation()
{
for( int i = 0 ; i < 24 ; i ++ )
p[i] = i;
}
Permutation( const int inp[] )
{
for( int i = 0 ; i < 24 ; i ++ )
p[i] = inp[i];
}
Permutation operator * ( const Permutation &q ) const
{
Permutation ret;
for( int i = 0 ; i < 24 ; i ++ )
ret.p[i] = q[p[i]];
return ret;
}
int operator [] ( const int &idx ) const { return p[idx]; }
};
const Permutation
rotVert( vertPerm ),
rotThru( thruPerm ),
rotCorn( cornPerm ),
turn[3][2] = {
{ Permutation( righCloPerm ), Permutation( righCouPerm ) },
{ Permutation( downCloPerm ), Permutation( downCouPerm ) },
{ Permutation( backCloPerm ), Permutation( backCouPerm ) }
};
struct CubeState
{
int cube[24];
CubeState()
{
for( int i = 0 ; i < 24 ; i ++ )
cube[i] = i;
}
CubeState( const Permutation& p )
{
for( int i = 0 ; i < 24 ; i ++ )
cube[i] = p[i];
Relabel();
}
void Perform( const Permutation& p )
{
static int tmp[24] = {};
for( int i = 0 ; i < 24 ; i ++ )
tmp[i] = cube[p[i]];
for( int i = 0 ; i < 24 ; i ++ )
cube[i] = tmp[i];
}
inline void RotVert() { Perform( rotVert ); }
inline void RotThru() { Perform( rotThru ); }
inline void RotCorn() { Perform( rotCorn ); }
inline void TurnRigh() { Perform( turn[0][0] ); }
inline void TurnDown() { Perform( turn[1][0] ); }
inline void TurnBack() { Perform( turn[2][0] ); }
inline void TurnRighCou() { Perform( turn[0][1] ); }
inline void TurnDownCou() { Perform( turn[1][1] ); }
inline void TurnBackCou() { Perform( turn[2][1] ); }
void Relabel()
{
static int app[6] = {};
for( int i = 0 ; i < 6 ; i ++ ) app[i] = 0;
for( int i = 0 ; i < 24 ; i ++ )
cube[i] = ( ( cube[i] >> 2 ) << 2 ) | ( app[cube[i] >> 2] ++ );
}
inline bool Chk()
{
static int tmp[3] = {};
tmp[0] = cube[2], tmp[1] = cube[5], tmp[2] = cube[8];
std :: sort( tmp, tmp + 3 );
return tmp[0] / 4 == 0 && tmp[1] / 4 == 1 && tmp[2] / 4 == 2;
}
void Regularize()
{
bool flg = false;
for( int i = 0 ; i < 4 ; i ++ )
{
for( int j = 0 ; j < 4 ; j ++ )
{
if( Chk() ) { flg = true; break; }
RotThru();
}
if( flg ) break; RotVert();
}
for( int j = 0 ; j < 3 ; j ++ )
{
if( cube[2] / 4 == 0 ) break;
RotCorn();
}
Relabel();
}
};
// use a permutation to describe a certain cube
// in Rubik's cube group, elements are permutations, rather than cubes themselves
// use the states of cubes to identify permutations
// Hence, in the hash table, states are keys and both of the permutations and steps to reach them are values
typedef std :: pair<Permutation, int> Node;
std :: queue<Node> q;
CantorIndex fac[24];
CantorIndex key[MAXN];
Permutation perm[MAXN];
int val[MAXN], nxt[MAXN];
int head[mod], tot;
Permutation Inversion( const Permutation &p )
{
Permutation ret;
for( int i = 0 ; i < 24 ; i ++ )
ret.p[p[i]] = i;
return ret;
}
CantorIndex Expand( const CubeState &s )
{
CantorIndex ret = 0;
for( int i = 0 ; i < 24 ; i ++ )
{
int cnt = 0;
for( int j = i + 1 ; j < 24 ; j ++ )
cnt += s.cube[j] < s.cube[i];
ret += fac[23 - i] * cnt;
}
return ret;
}
int GetHash( const CantorIndex &c ) { return c % mod; }
void Insert( const Permutation &nKey, const int nVal )
{
CantorIndex idx = Expand( CubeState( nKey ) );
int h = GetHash( idx );
for( int i = head[h] ; i ; i = nxt[i] )
if( key[i] == idx ) return ;
tot ++, perm[tot] = nKey, val[tot] = nVal, key[tot] = idx;
nxt[tot] = head[h], head[h] = tot;
}
int Find( const Permutation &nKey )
{
CantorIndex idx = Expand( CubeState( nKey ) );
int h = GetHash( idx );
for( int i = head[h] ; i ; i = nxt[i] )
if( key[i] == idx ) return i;
return -1;
}
int Find( const CubeState &cKey )
{
CantorIndex idx = Expand( cKey );
int h = GetHash( idx );
for( int i = head[h] ; i ; i = nxt[i] )
if( key[i] == idx ) return i;
return -1;
}
void Init()
{
fac[0] = 1;
for( int i = 1 ; i < 24 ; i ++ )
fac[i] = fac[i - 1] * i;
}
void Preprocess()
{
Node h; Permutation nxt;
Insert( Permutation(), 0 );
q.push( Node( Permutation(), 0 ) );
while( ! q.empty() )
{
h = q.front(); q.pop();
for( int i = 0 ; i < 3 ; i ++ )
for( int j = 0 ; j < 2 ; j ++ )
{
nxt = turn[i][j] * h.first;
if( Find( nxt ) == -1 )
Insert( nxt, h.second + 1 ),
q.push( Node( nxt, h.second + 1 ) );
}
}
}
int Translate( const char c )
{
int ret;
if( c == 'Y' ) ret = 0;
if( c == 'O' ) ret = 1;
if( c == 'B' ) ret = 2;
if( c == 'R' ) ret = 3;
if( c == 'G' ) ret = 4;
if( c == 'W' ) ret = 5;
return ret << 2;
}
int main()
{
Init();
Preprocess();
int T; read( T );
static char rubcan[100];
while( T -- )
{
CubeState ini, fin;
gets( rubcan );
rep( i, 0, 5 ) gets( str[i] );
for( int i = 0 ; i < 24 ; i ++ )
{
ini.cube[i] = Translate( str[pos[i][0]][pos[i][1]] );
fin.cube[i] = Translate( str[pos[i][0]][pos[i][1] + 9] );
}
ini.Regularize();
fin.Regularize();
Permutation iniP = perm[Find( ini )], finP = perm[Find( fin )];
write( val[Find( finP * Inversion( iniP ) )] ), putchar( '\n' );
}
return 0;
}