问题描述:
n个罪犯被一群人追杀,为了不被敌人杀死,n个罪犯决定通过如下的方式自杀:手拉手围成一圈,并对每个人按顺序编号为:1,2,3,...,n。
从1开始报数,报到k的那个人就自杀,再从自杀的下一个人继续从1开始报数,以此类推,我们能够知道最后幸存的那个人的编号吗?
解决方法:
通过DP能够在$O(n)$解决问题。
首先我们需要递推式:
设$f(n,k)$表示有n个人且报到k的人自杀的情况下,最后幸存的人的编号。
\[f(n,k)=(f(n-1,k)+k - 1)\%n + 1\]
其中 $f(1,k) = 1$。
如果有 $n$ 个人且报到 $k$ 的人自杀时,第一个自杀的人一定是编号为 $(k-1) \% n + 1$ 的人,因此剩下还有$n-1$ 个人,把 $f(n,k)$ 想象成一个子程序,你能够调用它,我们只需要把剩下的 $n-1$ 个人重新编号,就能用 $f(n-1,k)$ 解决了。重新编号如下:
原来的编号 1 2 ... k-1 k+1 k+2 ... n
现在的编号 n-k+1 n-k+2 n-1 1 2 n-k
假设现在编号为 $i$,则原来的编号为$(i+k-1) \% n + 1$(注意,这里不是(i+k) mod n,因为当i=n-k时,(i+k) mod n = 0)。
如果按照“现在的编号”的方式,用 $f(n-1,k)$ 能够找到幸存者,因此再将“现在的编号”转换成原来的编号即可,即 $f(n,k)=(f(n-1,k)+k - 1)\%n+1$
另解:如果按照 0,1,2,...,n-1 编号,则递推式变成了:
\[f(n,k)=(f(n-1,k)+k)\%n\]
其中$f(1,k)=0$
代码实现:
1 public class Josephus { 2 public static int josephus0(int n,int k){ 3 int r = 0; 4 for(int i=2;i<=n;i++){ 5 r = (r + k) % i; 6 } 7 return r; 8 } 9 public static int josephus1(int n,int k){ 10 int r = 1; 11 for(int i=2;i<=n;i++){ 12 r = (r + k - 1) % i + 1; 13 } 14 return r; 15 } 16 public static void main(String[] args) { 17 System.out.println(josephus1(10000,2)); 18 } 19 }
复杂度:$O(n)$