Skip to content

02814: 拨钟问题 ​

http://cs101.openjudge.cn/practice/02814/

有9个时钟,排成一个3*3的矩阵。

|-------|    |-------|    |-------|
|       |    |       |    |   |   |
|---O   |    |---O   |    |   O   |
|       |    |       |    |       |
|-------|    |-------|    |-------|
    A            B            C    

|-------|    |-------|    |-------|
|       |    |       |    |       |
|   O   |    |   O   |    |   O   |
|   |   |    |   |   |    |   |   |
|-------|    |-------|    |-------|
    D            E            F    

|-------|    |-------|    |-------|
|       |    |       |    |       |
|   O   |    |   O---|    |   O   |
|   |   |    |       |    |   |   |
|-------|    |-------|    |-------|
    G            H            I    
(图 1)

现在需要用最少的移动,将9个时钟的指针都拨到12点的位置。共允许有9种不同的移动。如下表所示,每个移动会将若干个时钟的指针沿顺时针方向拨动90度。

移动    影响的时钟
 
 1         ABDE
 2         ABC
 3         BCEF
 4         ADG
 5         BDEFH
 6         CFI
 7         DEGH
 8         GHI
 9         EFHI

输入

9个整数,表示各时钟指针的起始位置,相邻两个整数之间用单个空格隔开。其中,0=12点、1=3点、2=6点、3=9点。

输出

输出一个最短的移动序列,使得9个时钟的指针都指向12点。按照移动的序号从小到大输出结果。相邻两个整数之间用单个空格隔开。

样例输入

3 3 0 
2 2 2 
2 1 2

样例输出

4 5 8 9

来源:1166

这是一个经典的搜索或组合优化问题。由于每个移动(1-9号)执行 4 次后指针会回到原位,因此每个移动的次数只需在 0,1,2,3 之间选择。总的可能性为 49=262,144 种,这在 Python 的处理能力范围内。

以下是实现该逻辑的代码。我们使用嵌套循环遍历每种移动的组合,并利用早期剪枝(在循环中途检查部分时钟是否回零)来提高效率。

Python 代码实现

python
import sys

def solve():
    # 读取输入的9个整数(时钟初始状态)
    try:
        input_data = sys.stdin.read().split()
        if not input_data:
            return
        clocks = [int(x) for x in input_data]
    except EOFError:
        return

    # 变量 m1, m2, ..., m9 分别代表移动 1-9 执行的次数 (0-3次)
    # 根据题目给出的“移动影响的时钟”,我们可以推导出每个时钟受哪些移动的影响:
    # A (index 0): 1, 2, 4
    # B (index 1): 1, 2, 3, 5
    # C (index 2): 2, 3, 6
    # D (index 3): 1, 4, 5, 7
    # E (index 4): 1, 3, 5, 7, 9
    # F (index 5): 3, 5, 6, 9
    # G (index 6): 4, 7, 8
    # H (index 7): 5, 7, 8, 9
    # I (index 8): 6, 8, 9

    # 使用嵌套循环进行穷举。为了提速,我们在循环中间进行判断(剪枝)
    for m1 in range(4):
        for m2 in range(4):
            for m3 in range(4):
                for m4 in range(4):
                    # 检查时钟 A 是否能回到12点(0点)
                    if (clocks[0] + m1 + m2 + m4) % 4 != 0:
                        continue
                    for m5 in range(4):
                        # 检查时钟 B
                        if (clocks[1] + m1 + m2 + m3 + m5) % 4 != 0:
                            continue
                        for m6 in range(4):
                            # 检查时钟 C
                            if (clocks[2] + m2 + m3 + m6) % 4 != 0:
                                continue
                            for m7 in range(4):
                                # 检查时钟 D
                                if (clocks[3] + m1 + m4 + m5 + m7) % 4 != 0:
                                    continue
                                for m8 in range(4):
                                    # 检查时钟 G
                                    if (clocks[6] + m4 + m7 + m8) % 4 != 0:
                                        continue
                                    for m9 in range(4):
                                        # 检查剩余时钟 E, F, H, I
                                        if (clocks[4] + m1 + m3 + m5 + m7 + m9) % 4 != 0: continue
                                        if (clocks[5] + m3 + m5 + m6 + m9) % 4 != 0: continue
                                        if (clocks[7] + m5 + m7 + m8 + m9) % 4 != 0: continue
                                        if (clocks[8] + m6 + m8 + m9) % 4 != 0: continue
                                        
                                        # 找到解,格式化输出
                                        counts = [m1, m2, m3, m4, m5, m6, m7, m8, m9]
                                        result = []
                                        for i in range(9):
                                            for _ in range(counts[i]):
                                                result.append(str(i + 1))
                                        print(" ".join(result))
                                        return

if __name__ == "__main__":
    solve()

代码说明:

  1. 时钟状态:题目中 0,1,2,3 分别代表 12,3,6,9 点。每移动一次相当于状态加 1。我们只需要让 (初始状态 + 总移动次数) % 4 == 0 即可。
  2. 穷举与剪枝:虽然总共有 49 种情况,但通过在每一层循环后尽早判断对应的时钟是否已经归零,可以极大地减少不必要的计算。例如,一旦确定了 m1,m2,m4,时钟 A 的最终状态就固定了,如果此时它不为 0,则不需要继续往下循环。
  3. 唯一性:在线性代数中,这个系统可以看作是一个在模4运算下的线性方程组。由于其转换矩阵是可逆的,所以在 0∼3 范围内存在唯一的解,这组解对应的移动总数自然就是最短的。
  4. 输出:最后按照移动序号从小到大输出,满足题目要求。