用具体数学解决约瑟夫环问题

问题描述:

有 n 个人围成一个圈,每 q 个人踢掉一个人,问最后留下来的人是几号?

使用链表暴力求解时间复杂度是 O(qn),递归的话是 O(n) ,使用这个方法可以加速到 O(logn).


解法思路:

假设初始编号为1,2,3 … n,现在考虑一种新的编号方式。

第一个人不会被踢掉,那么他的编号从n开始往后加1,变成n+1,然后第二个人编号变为n+2,直到第q个人,他被踢掉了。

然后第q+1个人编号继续加1,变成了n+q,依次下去。

考虑当前踢到的人编号为kq,那么此时已经踢掉了k个人,所以接下去的人新的编号为n+k(q-1)+1…

所以编号为kq+d的人编号变成了n+k(q-1)+d,其中1<=d<q;

直到最后,可以发现活下来的人编号为qn,问题是怎么根据这个编号推出他原来的编号?

以 n=10 , q=3 为例,下图就是每个人新的编号:

12345678910
1112T1314T1516T17
18T1920T2122
T2324T25
26T27
28T
29
30

令:
N = n + k ( q − 1 ) + d \quad N=n+k(q-1)+dN=n+k(q1)+d
则他上一次的编号为:
k q + d = k q + N − n − k ( q − 1 ) = k + N − n \quad kq+d=kq+N-n-k(q-1)=k+N-nkq+d=kq+Nnk(q1)=k+Nn

又 ∵ k = N − n − d q − 1 = ⌊ N − n − 1 q − 1 ⌋ 又 \because k=\frac{N-n-d}{q-1}=\left\lfloor\frac{N-n-1}{q-1}\right\rfloork=q1Nnd=q1Nn1

所以他上一次的编号可以写为:
⌊ N − n − 1 q − 1 ⌋ + N − n \left\lfloor\frac{N-n-1}{q-1}\right\rfloor+N-nq1Nn1+Nn

因此最后存活的人可以这样计算:

N = qn
while N > n:
    N = k + N - n
ans = N

其中k等于:
k = ⌊ N − n − 1 q − 1 ⌋ k=\left\lfloor\frac{N-n-1}{q-1}\right\rfloork=q1Nn1
如果用D=qn+1-N代替N,那么算法可以简化为:
D = q n + 1 − N = q n + 1 − ( ⌊ ( q n + 1 − D ) − n − 1 q − 1 ⌋ + q n + 1 − D − n ) = n + D − [ ( q − 1 ) n − D q − 1 ] = D − ⌊ D q 1 ⌋ = D + ⌈ D q − 1 ⌉ = ⌈ q q − 1 D ⌉ \begin{array}{l} D=q n+1-N \\ =q n+1-\left(\left\lfloor\frac{(q n+1-D)-n-1}{q-1}\right\rfloor+q n+1-D-n\right) \\ =n+D-\left[\frac{(q-1) n-D}{q-1}\right] \\ =D-\left\lfloor\frac{D}{q 1}\right\rfloor \\ =D+\left\lceil\frac{D}{q-1}\right\rceil \\ =\left\lceil\frac{q}{q-1} D\right\rceil \end{array}D=qn+1N=qn+1(q1(qn+1D)n1+qn+1Dn)=n+D[q1(q1)nD]=Dq1D=D+q1D=q1qD
算法伪代码:

D = 1
while D <= (q-1)n:
    D = k
ans = qn + 1 - D

其中k等于:
k = ⌈ q q − 1 D ⌉ k=\left\lceil\frac{q}{q-1} D\right\rceilk=q1qD


代码展示:

//c++代码
#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

LL Ceil(LL x, LL y) {
  return x / y + (x % y != 0);
}

LL J(LL n, LL q) {
  LL D = 1, end = (q - 1) * n;
  while (D <= end) {
​    D = Ceil(q * D, q - 1);
  }
  return q * n + 1 - D;
}

int main() {
  LL n, q;
  while (~scanf("%lld%lld", &n, &q)) {
​    printf("%lld\n", J(n, q));
  }
  return 0;
}

版权声明:本文为weixin_45682081原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。