找回密码
 欢迎注册
楼主: 做题人生

[讨论] (2020 浙江高三竞赛题)环中取数

[复制链接]
 楼主| 发表于 2026-1-29 01:46:42 | 显示全部楼层
王守恩 发表于 2026-1-28 06:58
将1——2020的数字按顺时针方向围成一个圆圈,然后从1开始,按顺时针依次隔2个拿走1个,即拿走 1, 4, 7, .. ...

等有空了研究下约瑟夫环,就拿你这个做练习题吧。
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2026-1-29 13:47:00 | 显示全部楼层
不同间距的模拟结果:
\(\begin{array}{|c|r|c|r|c|r|}
\hline
\textbf{间距} & \textbf{剩下的数} & \textbf{间距} & \textbf{剩下的数} & \textbf{间距} & \textbf{剩下的数}\\
\hline
\text{2} & \text{1992} & \text{12} & \text{582} & \text{22} & \text{1925}\\
\text{3} & \text{665} & \text{13} & \text{338} & \text{23} & \text{183}\\
\text{4} & \text{1539} & \text{14} & \text{844} & \text{24} & \text{1553}\\
\text{5} & \text{1818} & \text{15} & \text{1800} & \text{25} & \text{830}\\
\text{6} & \text{254} & \text{16} & \text{498} & \text{26} & \text{1963}\\
\text{7} & \text{1911} & \text{17} & \text{17} & \text{27} & \text{1556}\\
\text{8} & \text{912} & \text{18} & \text{1215} & \text{28} & \text{609}\\
\text{9} & \text{590} & \text{19} & \text{927} & \text{29} & \text{628}\\
\text{10} & \text{1488} & \text{20} & \text{1195} & \text{30} & \text{1328}\\
\text{11} & \text{1788} & \text{21} & \text{1503} & \text{31} & \text{845}\\
\hline
\end{array}\)
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2026-1-29 16:09:56 | 显示全部楼层
{1992, 665, 1539, 1818, 254, 1911, 912, 590, 1488, 1788, 582, 338, 844, 1800, 498, 17, 1215, 927, 1195, 1503, 1925, 183, 1553, 830, 1963, 1556, 609, 628, 1328, 845}

Flatten@Table[Nest[Rest@RotateLeft[#, A] &, Range[2019], 2018] + 1, {A, 30}]——我们可以有万能公式。——谢谢 Jack315!!!谢谢 aimisiyou!!!
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2026-1-29 19:28:05 | 显示全部楼层
模拟取数过程的 Python 代码:
  1. class Ring:
  2.     def __init__(self, length: int = 10, gap: int = 2) -> None:
  3.         self.length = length
  4.         self.gap = gap
  5.         self.ring = None
  6.         self.sequence = None

  7.         self.reset()

  8.     def reset(self) -> None:
  9.         # 环中元素:[数字,是否被取走]
  10.         self.ring = [[i + 1, False] for i in range(self.length)]
  11.         # 取走数的过程。
  12.         self.sequence = []

  13.     def find_next(self, index: int) -> int:
  14.         gap = self.gap
  15.         while gap > 0:
  16.             # 索引循环遍历。
  17.             index += 1
  18.             if index >= self.length:
  19.                 index = 0

  20.             # 找到未被取走的数。
  21.             if not self.ring[index][1]:
  22.                 gap -= 1
  23.         return index

  24.     # 统计环中尚未被取走数字的数量。
  25.     def remain(self) -> int:
  26.         count = 0
  27.         for i in range(len(self.ring)):
  28.             if not self.ring[i][1]:
  29.                 count += 1
  30.         return count

  31.     # 模拟取数过程。
  32.     def simulate(self) -> None:
  33.         index = 0
  34.         while self.remain() > 1:
  35.             # 标志 index 处的元素被取走。
  36.             self.ring[index][1] = True
  37.             # 记录被取走的数。
  38.             self.sequence.append(self.ring[index][0])
  39.             # 查找下一个待取走的元素。
  40.             index = self.find_next(index)

  41.         # 模拟过程结束时, index 处的元素就是最后剩下的数字。
  42.         print(f'间距: {self.gap}')
  43.         print(f'剩下的数为: {self.ring[index][0]}')
  44.         print(f'取数过程: {self.sequence}')


  45. if __name__ == '__main__':
  46.     # 创建环 Ring 对象。
  47.     r = Ring()
  48.     # 设置环长度。
  49.     r.length = 2020
  50.     # 遍历不同的间距。
  51.     for g in range(2, 32):
  52.         r.gap = g
  53.         r.reset()
  54.         r.simulate()
复制代码
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2026-1-30 06:34:36 | 显示全部楼层
将1——n的数字按顺时针方向围成一个圆圈,然后从1开始, 按顺时针依次留A个,去B个,  这个过程一直进行下去, 直到剩下最后一个数字, 则最后剩下的数字是_____.

将1——n的数字按顺时针方向围成一个圆圈,然后从1开始, 按顺时针依次去B个,留A个,  这个过程一直进行下去, 直到剩下最后一个数字, 则最后剩下的数字是_____.

这2道题都会做, 也就差不多了。——也只是差不多!——A,B=正整数。要特别小心: 当剩下不足A+B时, 这个规矩不能变。
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2026-1-30 13:42:58 | 显示全部楼层
本帖最后由 Jack315 于 2026-1-30 14:59 编辑

使用双链表的 Python 代码。14# 的代码比较直白,这个代码性能比较高效。
  1. # 节点类。
  2. class Node:
  3.     def __init__(self, data=None):
  4.         self.data = data
  5.         self.prev = None
  6.         self.next = None

  7. # 双链表类。
  8. class DoublyLinkedList:
  9.     def __init__(self):
  10.         self.head = None
  11.         self.tail = None
  12.         self.current = None
  13.         self.size = 0

  14.     # 新节点在尾端插入。
  15.     def insert(self, data):
  16.         new_node = Node(data)
  17.         if self.size == 0:
  18.             new_node.prev = new_node
  19.             new_node.next = new_node
  20.             self.head = new_node
  21.             self.tail = new_node
  22.         else:
  23.             new_node.prev = self.tail
  24.             new_node.next = self.head
  25.             self.head.prev = new_node
  26.             self.tail.next = new_node
  27.             self.tail = new_node
  28.         self.size += 1

  29.     # 删除当前节点 current 。
  30.     def delete(self) -> Node or None:
  31.         if self.size == 0:
  32.             return None
  33.         else:
  34.             if self.current == self.head:
  35.                 self.head = self.head.next
  36.             if self.current == self.tail:
  37.                 self.tail = self.tail.prev
  38.             self.current.prev.next = self.current.next
  39.             self.current.next.prev = self.current.prev
  40.             self.size -= 1
  41.             return self.current

  42.     def move(self, steps) -> None:
  43.         for _ in range(steps):
  44.             self.current = self.current.next

  45.     def tolist(self):
  46.         result = []
  47.         node = self.head
  48.         for _ in range(self.size):
  49.             result.append(node.data)
  50.             node = node.next
  51.         return result

  52. # 环中取数模拟器类。
  53. class Simulator:
  54.     def __init__(self, length: int = 10) -> None:
  55.         self.length = length
  56.         self.ring = None
  57.         self.sequence = None

  58.     def simulate(self, gap: int = 2, num_left: int = 1) -> None:
  59.         # 初始化 ring 和 sequence
  60.         self.ring = DoublyLinkedList()
  61.         for i in range(self.length):
  62.             self.ring.insert(i + 1)
  63.         self.sequence = []
  64.         # 环中取数。
  65.         self.ring.current = self.ring.head
  66.         while self.ring.size > num_left:
  67.             removed_node = self.ring.delete()
  68.             self.ring.current = removed_node.next
  69.             self.ring.move(gap - 1)
  70.             self.sequence.append(removed_node.data)
  71.             del removed_node
  72.         # 输出结果。
  73.         print(f'间距: {gap}')
  74.         print(f'剩下的数为: {self.ring.tolist()}')
  75.         print(f'取数过程: {self.sequence}')

  76. if __name__ == "__main__":
  77.     w = Simulator(length=2020)
  78.     for g in range(2, 32):
  79.         w.simulate(gap=g, num_left=1)
复制代码
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2026-1-30 14:52:16 | 显示全部楼层
Jack315 发表于 2026-1-30 13:42
使用双链表的 Python 代表。14# 的代码比较直白,这个代码性能比较高效。

复杂度是O(N)吗?
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2026-1-30 15:27:20 | 显示全部楼层
【代码性能比较】环(列表)长度:2020,间隔:2~31 。
14# 代码的性能:7092 ms
Ring.png
16# 代码的性能:223 ms
DDLRing.png
估计用单链表也行,或许性能还能再改善一点。
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2026-1-30 17:23:09 | 显示全部楼层
对于步长k (比如原始约瑟夫问题我们可以看成是k=2),
那么我们可以先计算长度为2,3,...,k的的环的结果a(2),a(3),...,a(k) 其中显然a(2)=2.
对于\(n\ge k+1\), 那么计算a(n)时,由于用户第一步必然取走数字1,并且下一步会取数字k+1,
我们可以将数据重排为k+1,k+2,...,n,2,3,...,k,这是一个长度为n-1的序列,所以
如果\(a(n-1)\le n-k\), 那么a(n)=a(n-1)+k, 但是如果\(a(n-1)\gt n-k\), 那么必然有a(n)=a(n-1)-(n-k)+1.
这样就可以得到一个简单的递推计算关系。
特别的,在n相对k很大的情况,我们看a(n)的规律会发现几乎每次都是按步长k递增,直到到达某个a(n)>n+1-k, 下一个需要在加k后再减(n-1).
或者说每次n加1,a(n)-n的值增加k-1,直到某次增加到正数时需要在减去n-1. 于是我们可以批量处理,比如每次可以一次性处理将近\(\frac{n-1}{k-1}\)个数,来增加计算速度,快速逼近目标n
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
回复 支持 1 反对 0

使用道具 举报

发表于 2026-1-31 09:12:32 | 显示全部楼层
本帖最后由 Jack315 于 2026-1-31 12:45 编辑

【参考】 约瑟夫环——公式法(递推公式)
递推公式:\(f(N,G)=[f(N-1,G)+G]\mod N\)
其中:\(N\) 为取数时环的长度;\(G\) 为取数间距。
公式的推导是根据最后剩下的数在每轮取数时的位置。

将原始序列右移 \(G-1\) 个位置即得到与上文中相同的约瑟夫环。
因此计算结果修正为:\(计算结果+1-(G-1)=计算结果-G+2\)。

相应的 Python 代码:
  1. def Josephus(length: int, gap: int) -> int:
  2.     index = 0
  3.     for i in range(2, length + 1):
  4.         index = (index + gap) % i
  5.     result = index - gap + 2

  6.     # 修正序列移位导致的负数(越界)。
  7.     if result < 0:
  8.         result += length
  9.     return result

  10. if __name__ == '__main__':
  11.     for g in range(2, 32):
  12.         print(Josephus(2020, g))
复制代码

环(列表)长度:2020,间隔:2~31,运行时间 3ms 。

点评

这个链接解释得清楚!  发表于 2026-1-31 22:23
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
回复 支持 1 反对 0

使用道具 举报

您需要登录后才可以回帖 登录 | 欢迎注册

本版积分规则

小黑屋|手机版|数学研发网 ( 苏ICP备07505100号 )

GMT+8, 2026-5-1 19:10 , Processed in 0.026598 second(s), 19 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表