Time limit: 30.000 seconds
限時30.000秒ios
There was once a 3 by 3 by 3 cube built of 27 smaller cubes. It has fallen apart into seven pieces:
寫曾經有一個3x3x3的立方塊,由27個小立方塊構成。它被切分爲了以下七個碎片:算法
Figure 1: The seven pieces that once formed a cube
圖1:組成立方體的七塊碎片數組
The seven pieces can be assembled in many ways to again form the cube. Figure 2 shows one of these possibilities. The first square stands for the front plane, the next one for the middle plane and the last one for the back plane of the cube. The letters in the cells stand for the name of piece filling out the corresponding space in the cube. The name of the seven pieces can be found in figure 1.
這七個碎片能夠由多種方式從新組成原先的立方塊,圖2給出了其中一種組裝方法。第一個正方形表明前平面,下一個表明中平面,最後一個表明後平面。方格中的字母表明被放在對應位置上的碎片的名稱。這七個碎片的名稱能夠在圖1中看到。數據結構
Figure 2: Two possibilities of assembling the cube
圖2:由碎片組成立方體的兩種可能方式函數
You are to write a program that outputs all possibilities of assembling the cube but suppress solutions that are mere rotations of another solution.
請你寫一個程序輸出組成立方體的全部可能方式,但不要輸出僅僅是旋轉角度不一樣的重複解。測試
Hint: Piece a is the only part that, by rotation and translation, cannot be transformed into itself. In order to avoid solutions that are mere rotations of an already found solution, you may restrict transformations of piece a to translations.
提示:碎片a是僅有一塊不論如何旋轉或平移都不可能變回原形的碎片,爲避免重複計算僅旋轉角度不一樣的多個解,你應當將a的變換限制爲僅可平移。
優化
The input file has several test cases. Each test case indicates the initial position of piece `a'. You can translate it, but you mustn't rotate it.
輸入文件包含多個測試例子,每一個測試例子指名了初始的‘a’的位置。你能夠平移‘a’,但不能旋轉。ui
For each solution found, your program should output a line containing the solution as a string. The string is a linearized form of the cube. Each letter stands for the piece filling out the corresponding space in the cube. It is linearized as follows:
對於找到的每個解,你的程序應輸出行包含解的字符串。該字符串是對立體的線性化。每一個字母表明碎片填入立方體中的位置。以以下方式線性化:
this
The solutions in figure 2 would be represented like this:
圖二所示的解表達以下:
adcaccaacddgbfgffedggbfebee
aababbadcffegfcddcfeeggedgc
It is very important that your program uses the naming convention given in figure 1 and linearizes the cube as explained above.
記住,你的程序必定要按照圖1的命名約定來線性化立方體。spa
Print a blank line after each test case.
每個測試例子結束後打印一行空行。
Figure 3: Positions of the cells in the string
圖3:方格在字符串中的位置
Figure 3 again shows how the cells of the cube are linearized.
圖3給出了立方體的方格被線性化的另外一個示例。
aa.a..a....................
.........a..a..aa..........
aababbadcggeffcddcgeegfedfc
aababbadceffgdcgdceefedfggc
...
aababbadcffegfcddcfeeggedgc
adcaccaacfddfebgeeffdggbgeb
...
難題,對調試技巧要求高。
七塊碎片的模式是由圖中給定的,不可以改變它的形狀。要求的解就是由這七塊碎片以某種組合方式(不一樣的空間位置)來組合成爲一個完整的立方體。題目給出的時間有30秒之多,這樣看來就須要暴力搜索了。考慮立方體由27小塊構成,根據題目中給出的線性化要求,每一個小塊對應的空間座標是有其固定編號的。咱們按照直角座標系對每一個小塊標定整數座標:
1: (0, 2, 2), 2: (1, 2, 2), 3: (2, 2, 2),
4: (0, 1, 2), 5: (1, 1, 2), 6: (2, 1, 2),
7: (0, 0, 2), 8: (1, 0, 2), 9: (2, 0, 2),
10: (0, 2, 1), 11: (1, 2, 1), 12: (2, 2, 1),
13: (0, 1, 1), 14: (1, 1, 1), 15: (2, 1, 1),
16: (0, 0, 1), 17: (1, 0, 1), 18: (2, 0, 1),
19: (0, 2, 1), 20: (1, 2, 1), 21: (2, 2, 1),
22: (0, 1, 1), 23: (1, 1, 1), 24: (2, 1, 1),
25: (0, 0, 1), 26: (1, 0, 1), 27: (2, 0, 1),
事實上,這些小塊的座標是能夠用程序循環求出的,這裏給出所有取值只是爲了方便後面的算法描述。觀察7塊碎片,除b爲3小塊構成以外,其它均由4小塊構成。這些碎片組成立方體時,其中的每一個小塊必定是位於(0, 0, 0)到(2, 2, 2)的座標內的。這裏要定義一個概念:構形。構形是指一個碎片在立方體中經過旋轉或平移達到的一種形態,而且其每個小塊都沒有超出立方體的座標邊界。若是用27個2進制數分別表明立方體中的27個座標是否存在小方塊(1表示對應位置有一個小方塊,0表示沒有),那麼這27個數必定能夠表示這7塊碎片的任何一種構形。一個unsigned long型整數32位,足夠表示任一塊碎片的任一種形態。若是分別表明7塊碎片的各自一種形態的7個2進制數:(b1 | b2 | b3 | b4 | b5 | b6 | b7) = 0x07ffffff,就說明7塊碎片的這些形態完整的組成了立方體。設立方體c中沒有聽任何碎片時c=0,放入一個碎片b,即c:=c|b。若是b所需的位置在c中已經存在小方塊,則有:~b & c != c。有了上述這些表達式方,填充算法的思路就很清晰了。咱們只要生成每一種碎片的全部構形,而後用DFS的方式(深度爲不一樣碎片)便可暴力搜索出全部可能的組合方式。
接下來的問是如何生成每一種碎片的全部構形。方式有不少種,這裏採用三維座標變換中的旋轉矩陣方式,見維基百科:旋轉矩陣。一個碎片的旋轉有最多有24種不一樣的方式,事實上只有碎片a存在24種,其它的碎片的24種旋轉中必定有一些轉完的結果是相同的(這裏須要一點空間想象力)。首先要構造這24種旋轉矩陣(3x3矩陣),它們必定是x旋轉、y旋轉和z旋轉的組合,每一種旋轉的角度可選用0、π/2、π和-π/2。這樣會生成64種矩陣,去重後就會剩下不一樣的24種。接下來以座標的形式手動定義每一塊碎片,而後求出每一塊碎片的24種不一樣旋轉(矩陣乘法)。這裏要注意的時,不管是最初手動定義的碎片仍是旋轉後產生的新碎片,都要經過平移使之頂住立方體的x=0、y=0和z=0三個平面,也就是讓他們在不越出座標邊界的前提下儘量的靠近立方體的座標原點。生成每個碎片的全部旋轉以後,就能夠對這每一種旋轉進行平移,從而獲得每個碎片的全部構形。
最後要注意幾點:
#include <algorithm> #include <iostream> #include <vector> #include <string> #include <memory.h> struct POINT3 // 定義每一個方塊的座標的結構體 { int x; int y; int z; }; typedef std::vector<POINT3> COORDS; // 類型定義,表示圖中7個piece的座標 typedef unsigned long FORMMASK; // 類型定義,表示每個piece通過旋轉、偏移以後產生的一種構型的掩碼 typedef std::vector<FORMMASK> VECFORMMASK; // 類型定義,一個piece的掩碼集合 typedef std::vector<VECFORMMASK> ALLPIECEFORMS; // 類型定義,全部piece的掩碼集合 typedef std::vector<int> MATRIX; // 類型定義,表示矩陣 typedef std::vector<MATRIX> ROTMATS; // 類型定義,表示矩陣的集合 ALLPIECEFORMS allPieceForms; // 全局變量,存儲全部piece通過旋轉、偏移後的掩碼 ALLPIECEFORMS result; // 全局變量,存儲最終的結果 ROTMATS rotMats; // 全局變量,存儲旋轉矩陣 // 單位矩陣分別繞X、Y、Z軸旋轉0°、90°、180°、270°後對應的矩陣 int rotateMatX[][9] = { {1, 0, 0, 0, 1, 0, 0, 0, 1}, {1, 0, 0, 0, 0, -1, 0, 1, 0}, {1, 0, 0, 0, -1, 0, 0, 0, -1}, {1, 0, 0, 0, 0, 1, 0, -1, 0} }; int rotateMatY[][9] = { {1, 0, 0, 0, 1, 0, 0, 0, 1}, {0, 0, 1, 0, 1, 0, -1, 0, 0}, {-1, 0, 0, 0, 1, 0, 0, 0, -1}, {0, 0, -1, 0, 1, 0, 1, 0, 0} }; int rotateMatZ[][9] = { {1, 0, 0, 0, 1, 0, 0, 0, 1}, {0, -1, 0, 1, 0, 0, 0, 0, 1}, {-1, 0, 0, 0, -1, 0, 0, 0, 1}, {0, 1, 0, -1, 0, 0, 0, 0, 1} }; // 題目圖中7中piece相應的空間座標,順序爲a,f,g,e,c,d,b,以左下角爲原點(0,0,0) POINT3 pieces[][4] = { {{0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {0, 2, 0}}, {{0, 0, 0}, {1, 0, 0}, {1, 0, 1}, {1, 1, 1}}, {{0, 0, 0}, {1, 0, 0}, {1, 0, 1}, {0, 1, 0}}, {{0, 0, 0}, {0, 0, 1}, {1, 0, 1}, {0, 1, 1}}, {{0, 1, 0}, {1, 0, 0}, {1, 1, 0}, {1, 2, 0}}, {{0, 0, 0}, {0, 0, 1}, {1, 0, 1}, {1, 0, 2}}, {{0, 0, 0}, {1, 0, 0}, {0, 0, 1},} }; char nameMap[] = {'a', 'f', 'g', 'e', 'c', 'd', 'b'}; // 直角座標系與題目座標系的位置編號對應表 char posMap[] = {24, 25, 26, 21, 22, 23, 18, 19, 20, 15, 16, 17, 12, 13, 14, 9, 10, 11, 6, 7, 8, 3, 4, 5, 0, 1, 2}; bool operator < (const POINT3 &p1, const POINT3 &p2) { return (p1.x < p2.x || (p1.x == p2.x && p1.y < p2.y) || (p1.x == p2.x && p1.y == p2.y && p1.z < p2.z)); } bool operator == (const POINT3 &p1, const POINT3 &p2) { return (p1.x == p2.x && p1.y == p2.y && p1.z == p2.z); } // 計算矩陣乘法,輸入兩個矩陣pM1,pM2,並輸入第一個矩陣的行數,第二個矩陣的列數以及矩陣的大小,輸出矩陣相乘的結果pR。 void MatMul(int *pM1, int *pM2, int r1, int c2, int n, int *pR) { for (int i = 0; i < r1; ++i) { for (int j = 0; j < c2; ++j) { pR[i * c2 + j] = 0; for (int k = 0; k < n; ++k) pR[i * c2 + j] += pM1[i * r1 + k] * pM2[k * c2 + j]; } } } // 求座標的相反數 POINT3 Inverse(POINT3 &coord) { coord.x = -coord.x; coord.y = -coord.y; coord.z = -coord.z; return coord; } // 找出一個piece中座標最大的點和座標最小的點 void Bound(const COORDS &coords, POINT3 &min, POINT3 &max) { min = max = coords[0]; for (size_t i = 1; i < coords.size(); ++i) { if (coords[i].x < min.x) min.x = coords[i].x; if (coords[i].x > max.x) max.x = coords[i].x; if (coords[i].y < min.y) min.y = coords[i].y; if (coords[i].y > max.y) max.y = coords[i].y; if (coords[i].z < min.z) min.z = coords[i].z; if (coords[i].z > max.z) max.z = coords[i].z; } } // 根據piece中座標的最小值,標記一個piece進行偏移轉換時能偏移的最大位置 void PieceSize(const COORDS &coords, POINT3 &size) { POINT3 minCoord; Bound(coords, minCoord, size); size.x -= minCoord.x; size.y -= minCoord.y; size.z -= minCoord.z; } // piece的偏移轉換 void Translate(COORDS &coords, const POINT3 &dist) { for (size_t i = 0; i < coords.size(); ++i) { coords[i].x += dist.x; coords[i].y += dist.y; coords[i].z += dist.z; } } // 計算一個piece的掩碼,根據piece中各個方塊的位置座標計算其在最終的結果序列中出現的位置。 FORMMASK Coords2Mask(COORDS &coords) { FORMMASK ulMask = 0; for (COORDS::iterator j = coords.begin(); j != coords.end(); ++j) { ulMask |= (1 << (j->x + j->y * 3 + j->z * 9)); } return ulMask; } // 旋轉、偏移轉換3×3×3方塊,forms記錄給定piece的全部構型掩碼 void ExpandForms(const POINT3 *pPiece, int n, VECFORMMASK &forms) { // 從數據指針構造座標數組,並排序 COORDS coords(pPiece, pPiece + n), rotCoords(n); POINT3 minCoord, size; std::sort(coords.begin(), coords.end()); // rots記錄給定piece的全部旋轉 std::vector<COORDS> rots; // 求出全部的旋轉構型,包括不旋轉的原構型(單位陣),每個旋轉構型最多有24個不一樣構型 for (ROTMATS::iterator i = rotMats.begin(); i != rotMats.end(); ++i) { for (int j = 0; j < n; ++j) { MatMul(i->data(), (int*)&coords[j], 3, 1, 3, (int*)&rotCoords[j]); } // 規格化每一種旋轉構型,使旋轉後的方塊仍以左下角的小方塊爲原點 Bound(rotCoords, minCoord, size); Translate(rotCoords, Inverse(minCoord)); std::sort(rotCoords.begin(), rotCoords.end()); rots.push_back(rotCoords); } // 去除重複的旋轉構型 std::sort(rots.begin(), rots.end()); rots.erase(std::unique(rots.begin(), rots.end()), rots.end()); size_t nRotCnt = rots.size(); // 將每一種旋轉構型偏移,產生新的偏移構型,每個piece均有6個偏移構型 for (size_t i = 0; i != nRotCnt; ++i) { PieceSize(rots[i], size); for (minCoord.x = 0; minCoord.x < 3 - size.x; ++minCoord.x) { for (minCoord.y = 0; minCoord.y < 3 - size.y; ++minCoord.y) { for (minCoord.z = 0; minCoord.z < 3 - size.z; ++minCoord.z) { if (minCoord.x != 0 || minCoord.y != 0 || minCoord.z != 0) { rots.push_back(rots[i]); Translate(rots.back(), minCoord); } } } } } // 計算相應構型的掩碼 for (std::vector<COORDS>::iterator i = rots.begin(); i != rots.end(); ++i) { forms.push_back(Coords2Mask(*i)); } } // 遞歸的查找題中的7種piece的組合結果,path中記錄每一種組合的全部piece的掩碼 void FillCube(ALLPIECEFORMS::const_iterator curForm, FORMMASK nCube, VECFORMMASK &path) { // 遞歸結束條件,每一種組合的掩碼徹底填充 if (nCube == 0x07ffffff) { result.push_back(path); return; } const FORMMASK *pForms = curForm->data(); size_t nFormCnt = curForm->size(); for (size_t i = 0; i < nFormCnt; ++i) { // 判斷當前的掩碼中是否有某一piece的掩碼,若沒有,則修改當前掩碼,並在path中記錄piece if (((~pForms[i]) & nCube) == nCube) { unsigned long nTmpCube = nCube | pForms[i]; path.push_back(pForms[i]); FillCube(curForm + 1, nTmpCube, path); path.pop_back(); } } } int main(void) { // 計算旋轉矩陣,共計24種旋轉狀況,如:繞XY軸旋轉90°,繞XZ軸旋轉180°等 for (int i = 0; i < 4; ++i) { for (int j = 0; j < 4; ++j) { for (int k = 0; k < 4; ++k) { int tmp1[9]; rotMats.push_back(std::vector<int>(9)); MatMul(rotateMatY[j], rotateMatZ[k], 3, 3, 3, tmp1); MatMul(rotateMatX[i], tmp1, 3, 3, 3, rotMats.back().data()); } } } std::sort(rotMats.begin(), rotMats.end()); rotMats.erase(std::unique(rotMats.begin(), rotMats.end()), rotMats.end()); // 初始化piece的全部旋轉和偏移的構型 allPieceForms.resize(7); for (int i = 0; i < 7; ++i) { ExpandForms(pieces[i], 4 - (i / 6), allPieceForms[i]); } for (std::string strLine; getline(std::cin, strLine) && !strLine.empty(); ) { // 計算讀入的數據中a的位置 COORDS inputA; result.clear(); for (std::string::iterator i = strLine.begin(); i != strLine.end(); ++i) { if (*i == 'a') { int nIdx = i - strLine.begin(); POINT3 tmp = {nIdx % 3, 2 - (nIdx % 9) / 3, 2 - nIdx / 9}; inputA.push_back(tmp); } } // 將讀入的pieceA的座標規格化爲空間直角座標系並創建相應的掩碼 POINT3 minCoord, tmp; Bound(inputA, minCoord, tmp); Translate(inputA, Inverse(minCoord)); std::sort(inputA.begin(), inputA.end()); Coords2Mask(inputA); // 查找讀入的pieceA轉化到空間直角座標系後相應的構型 int nRot = std::find(allPieceForms[0].begin(), allPieceForms[0].end(), Coords2Mask(inputA)) - allPieceForms[0].begin(); // 計算pieceA對應的全部組合 FORMMASK fixedA = allPieceForms[0][nRot]; VECFORMMASK vfm(1, fixedA); FillCube(allPieceForms.begin() + 1, fixedA, vfm); // 計算pieceA通過偏移處理後的全部組合 for (int i = nRot * 5 + 24; i < (nRot + 1) * 5 + 24; ++i) { fixedA = allPieceForms[0][i]; vfm = VECFORMMASK(1, fixedA); FillCube(allPieceForms.begin() + 1, fixedA, vfm); } // 將全部掩碼的組合轉換爲輸出形式的字符串 std::vector<std::string> outStrs; for (ALLPIECEFORMS::iterator i = result.begin(); i != result.end(); ++i) { std::string str; str.resize(27); for (size_t j = 0; j < i->size(); ++j) { FORMMASK mask = i->at(j); for (size_t k = 0; k < 27; ++k) { if (mask & (1 << posMap[k])) { //判斷條件中posMap[k]爲將空間直角座標系對應到題中座標系的位置,調整輸出字符串的順序,與題中輸出要求相符 str[k] = nameMap[j]; } } } outStrs.push_back(str); } std::sort(outStrs.begin(), outStrs.end()); for (std::vector<std::string>::iterator i = outStrs.begin(); i != outStrs.end(); ++i) { std::cout << *i << std::endl; } std::cout << std::endl; } return 0; }