问题描述:
有 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 为例,下图就是每个人新的编号:
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|
| 11 | 12 | T | 13 | 14 | T | 15 | 16 | T | 17 |
| 18 | T | 19 | 20 | T | 21 | 22 | |||
| T | 23 | 24 | T | 25 | |||||
| 26 | T | 27 | |||||||
| 28 | T | ||||||||
| 29 | |||||||||
| 30 | |||||||||
令:
N = n + k ( q − 1 ) + d \quad N=n+k(q-1)+dN=n+k(q−1)+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+N−n−k(q−1)=k+N−n
又 ∵ 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\rfloor又∵k=q−1N−n−d=⌊q−1N−n−1⌋
所以他上一次的编号可以写为:
⌊ N − n − 1 q − 1 ⌋ + N − n \left\lfloor\frac{N-n-1}{q-1}\right\rfloor+N-n⌊q−1N−n−1⌋+N−n
因此最后存活的人可以这样计算:
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=⌊q−1N−n−1⌋
如果用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+1−N=qn+1−(⌊q−1(qn+1−D)−n−1⌋+qn+1−D−n)=n+D−[q−1(q−1)n−D]=D−⌊q1D⌋=D+⌈q−1D⌉=⌈q−1qD⌉
算法伪代码:
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=⌈q−1qD⌉
代码展示:
//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;
}