问题描述
n 个编号从 1,⋯,n 的人逆时针站成一圈,开始从 1 号开始,每次从当前人开始数 k 个,然后这个人出局,求最后一个人编号多少?
该问题由约瑟夫 (Titus Flavius Josephus),于公元一世纪提出,他当时求解的是 n=41,k=2 的情况 (maybe (o゚v゚)ノ
但他还是很强呀)。
线性算法
设 f(n,k) 表示规模为 n,k 的Josephus问题的解(注:这里编号是从0开始计数的,所以最终要将答案+1),于是有如下递推式:
f(n,k)=(f(n−1,k)+k)modn
该递推式不难理解,从 0 开始数 k 个,k−1 号出局,则 k 就是 n−1 个人的局中开始的那个,于是相对来说他在 n−1 的局中又作为 0 号位开始,于是 n−1 局中的解在做相对位移 k 后就是 n 的局中的答案了。
优化
当 k 比较小,n 比较大的情况,线性算法就显得有点力不从心了。
观察递推式 f(n,k)=(f(n−1,k)+k)modn 可以发现,如果当 n 非常大的时候,modn 在该式中就无法发挥作用,那么是不是就可以考虑对 k 直接求和,在保证 modn 不起作用的前提下。
考虑一个顺推,设 F=f(n,k),x 为当前状态 f(n,k) 能最多往后面直接累加 x 个 k,则有:
f(n+x,k)⇒F+xkxx=f(n,k)+xk<n+x<n+x<⌊k−1n−F⌋⩽⌊k−1n−F−1⌋
可以证明,这样做的复杂度为 O(klogn),证明见 OI-Wiki 约瑟夫问题 对数算法。
仔细观察这个写法的时间复杂度其实是 O(min(n,klogn)),因为 while
中间的 if
判断了当 k 较小的情况。
练习
- 2018-2019 ACM-ICPC, Asia Shenyang Regional Contest K - Let the Flames Begin