Consider nine clocks arranged in a 3x3 array thusly:
|-------| |-------| |-------|
| | | | | | |
|---O | |---O | | O |
| | | | | |
|-------| |-------| |-------|
A B C
|-------| |-------| |-------|
| | | | | |
| O | | O | | O |
| | | | | | | | |
|-------| |-------| |-------|
D E F
|-------| |-------| |-------|
| | | | | |
| O | | O---| | O |
| | | | | | | |
|-------| |-------| |-------|
G H I
The goal is to find a minimal sequence of moves to return all the dials to 12 o'clock. Nine different ways to turn the dials on the clocks are spplied via a table below; each way is called a move. Select for each move a number 1 through 9 which will cause the dials of the affected clocks (see next table) to be turned 90 degrees clockwise.
Move | Affected clocks |
1 | ABDE |
2 | ABC |
3 | BCEF |
4 | ADG |
5 | BDEFH |
6 | CFI |
7 | DEGH |
8 | GHI |
9 | EFHI |
Example
Each number represents a time accoring to following table:9 9 12 9 12 12 9 12 12 12 12 12 12 12 12
6 6 6 5 -> 9 9 9 8-> 9 9 9 4 -> 12 9 9 9-> 12 12 12
6 3 6 6 6 6 9 9 9 12 9 9 12 12 12
[But this might or might not be the `correct' answer; see below.]
PROGRAM NAME: clocks
INPUT FORMAT
Lines 1-3: | Three lines of three space-separated numbers; each number represents the start time of one clock, 3, 6, 9, or 12. The ordering of the numbers corresponds to the first example above. |
SAMPLE INPUT (file clocks.in)
9 9 12
6 6 6
6 3 6
OUTPUT FORMAT
A single line that contains a space separated list of the shortest sequence of moves (designated by numbers) which returns all the clocks to 12:00. If there is more than one solution, print the one which gives the lowest number when the moves are concatenated (e.g., 5 2 4 6 < 9 3 1 1).
SAMPLE OUTPUT (file clocks.out)
4 5 8 9
分析
这道目的一种解法是——穷举。每种移动方法最多出现3次,超过3次就无意义了。所以总共有4^9=2^18=256K种可能。基本上用穷举在很短的时间内就可以求出来了。- 由于每种移动方法最多出现三次,故用一个32位整数就可以表示当前的移动方法(用9×3个bit)。
- 利用递归进行穷举的时候,可以剪枝来减小穷举的量。
#include <stdio.h>
#include <assert.h>
int rules[10][9] = { {0,0,0,0,0,0,0,0,0}, {3,3,0,3,3,0,0,0,0}, {3,3,3,0,0,0,0,0,0},
{0,3,3,0,3,3,0,0,0}, {3,0,0,3,0,0,3,0,0}, {0,3,0,3,3,3,0,3,0},
{0,0,3,0,0,3,0,0,3}, {0,0,0,3,3,0,3,3,0}, {0,0,0,0,0,0,3,3,3},
{0,0,0,0,3,3,0,3,3}};
int getMinMoves(int clocks[9], int rule, int curMove);
void print(FILE* fout, int minMove);
int main(void)
{
int clocks[9];
int i;
int min_moves;
FILE* fin = fopen("clocks.in", "r");
FILE* fout = fopen("clocks.out", "w");
assert(fin != NULL && fout != NULL);
for (i = 0; i < 9; i ++)
{
fscanf(fin, "%d", clocks+i);
if (clocks[i] == 12)
{
clocks[i] = 0;//for arithmetic convenience
}
}
min_moves = getMinMoves(clocks, 1, 0);
print(fout, min_moves);
fclose(fin);
fclose(fout);
return 0;
}
int getMinMoves(int clocks[9], int rule, int curMove)
{
int tempMove = 0;//record the search result of the subtree
int minMove = 1 << 29;//record the mini of search results, initially set to a big value
int curClocks[9];//current clocks
int i,j;
memcpy(curClocks, clocks, sizeof(int)*9);
for (i = 0; i < 4; i ++) // at most move 3 times according to curren rule
{
if ( i != 0)//if move clocks according to the current rule
{
curMove = curMove | (1 << ((rule -1)*3 + i - 1));
for (j = 0; j < 9; j ++)
{
curClocks[j] = (clocks[j] + rules[rule][j] * i) % 12;
}
}
for (j = 0; j < 9 && curClocks[j] == 0; j ++)
{
}
if (j == 9)
{
return curMove;//if found in current rule, then update
}
if (rule < 9) // if not the last rule (9), then search the subtree
{
tempMove = getMinMoves(curClocks, rule + 1, curMove);
if ((tempMove > 0) && (tempMove < minMove))
{
minMove = tempMove;
}
}
}
//if not results from the current search, then check the result from subtree
if (rule == 9)
{
return 0;// last rule, so nothing found
}
else{
if (minMove == (1 << 29))
{
return 0;// keep unchanged, nothing found
}
else{
return minMove;
}
}
};
void print(FILE* fout, int minMove)
{
int i = 0;
int j = 1;
int found = 0;
do
{
if (j & minMove)
{
if (found)
{
fprintf(fout, " ");
}
fprintf(fout, "%d", i / 3 + 1);
found = 1;
}
j = j << 1;
i ++;
} while (i < 27);
fprintf(fout, "/n");
}
这个算法从第一种移动方法开始遍历至第9种移动方法以满足要求数字最小。唯一的剪枝发生在黑体字处,如果在遍历第n种做法的时候找到了合适的,则立即返回,因为这个必定是最小的。可以这样看,如果当前找到的数字是“xxxnn”,那么 首先可以排除有“xxx(n-1)”这样的数字,然后有的必然是“xxx(n+1)……”这样的字符串 ,假设现在需要k个n(1<=k<=3),那么要用其它的移动方法来代替k个n,其数量>=k。唯一相等的情况发生在某个移动方法和第n种方法完全一致的情况下。由于我是从第1种移动方法遍历到第9种,这样的话那个和第n种 移动方法一致的移动方法的序号一定>n,所生成的数字一定大,故剪枝。