**越来越懒了。之前总是把题目描述敲键盘敲进去,太费劲了,碰上图的话就更痛苦。 拍个照直接贴上去就简单多了^-^.
6个骰子构成的排列构成的总的状态数为6的6次方即6^6=46656种。
从任意的状态出发经过翻转骰子的操作更新状态,其历经的状态序列都必然是如下图所示:
其中上边代表一般性情况,下边可以看作是一种特殊情况。
为什么必然会成为上图这种情况呢?根本原因在于这是一个有限状态序列(如前所述最多只有46656种状态)。从任意状态出发,最多经过46656步以后,必然会回到前面已经经过的某个状态(根据组合计数中的“鸽笼原则”或者“抽屉原则”)。这个洞见(Insight)是解决本题的关键。
用acyclic存储已经判明的属于非循环的状态(即从它自己出发不会回到自身),用cyclic存储已经判明的属于循环的状态(即从它自己出发会回到自身)。
从任意状态出发进行翻转更新,将历经的状态序列存储在statelst中:
途中(包括一开始它自身)不管是碰到已判明是acyclic的还是cyclic的状态,都可以判断从出发状态到此刻为止的状态都是属于acyclic(为什么?请大家仔细品一品),将它们加入到acyclic
如果途中某个状态已经存在于statelst(即上图所示情况),则如上图所示这表明在状态序列中到该状态之前的所有的状态都属于acyclic,其后(包括它自己)都属于cyclic,将这些状态分别加入acyclic和cyclic
对所有46656种状态进行遍历(分别以它们为起始状态)重复以上过程即可。
Acyclic和cyclic存储已经判明的结果,是为了避免重复搜索。毕竟,比如说,一旦你从某个状态出发已经找到了一个loop,那这个loop中的所有的状态都属于循环状态了,自然不必再针对其中每个状态重新搜索一次。
- # -*- coding: utf-8 -*-
- """
- Created on Thu Sep 23 07:49:36 2021
-
- @author: chenxy
- """
-
- import sys
- import time
- import datetime
- import random
- from typing import List
- from collections import deque
- import itertools as it
- import numpy as np
-
- def getnext(cur:tuple)->tuple:
- cur_list = list(cur)
- # print(cur_list)
- c0 = cur[0]
- for k in range(c0):
- cur_list[k] = 7-cur_list[k]
- return tuple(cur_list[c0:] + cur_list[0:c0])
-
- # print(getnext((1,1,6,1,6,1)))
-
- tStart = time.perf_counter()
- cyclic = set()
- acyclic = set()
- for state in it.product([1,2,3,4,5,6],repeat=6):
- # print(state)
- cur = state
- statelst = []
- while 1:
- if (cur in acyclic) or (cur in cyclic):
- for s in statelst:
- acyclic.add(s)
- break
- if cur in statelst:
- beforeCur = True
- while len(statelst) > 0:
- s = statelst.pop(0)
- if s == cur:
- beforeCur = False
- if beforeCur:
- acyclic.add(s)
- else:
- cyclic.add(s)
- break
- statelst.append(cur)
- cur = tuple(getnext(list(cur)))
- # print(cur)
-
- tCost = time.perf_counter() - tStart
-
- print('total_count = {0}, tCost = {1:6.3f}(sec)'.format(len(acyclic),tCost))
运行结果:total_count = 28908, tCost = 0.096(sec)
这是到目前为止最令自己满意的一道题目。