约瑟夫问题是个有名的问题:N个人围成一圈,编号由0到N-1,从第一个开始报数,第K个将出局,最后剩下一个人。例如N=6,K=5,出局人编号的顺序是:4,3,5,1,2,赢家是0。
这里给出一个十分简洁的递归解法:
#include <bits/stdc++.h>
int solve(int n, int k) {
if (n == 1) return 0;
else return (solve(n - 1, k) + k) % n;
}
int main() {
int N, K;
scanf("%d %d", &N, &K);
printf("%d", solve(N, K));
return 0;
}
理解:
solve(n,k)的意思是,有n人,以k为步长删人的约瑟夫问题的解。那么显然solve(1,k)=0,即只有1个人的时候,胜者就是编号为0的这一个人。
若不是1个人,有n人,删掉第k人(编号k-1)之后变成了n-1人,下次起始的地方是从这个出局的人后面开始算的,不妨把该开始位置的人编号“看成“0。
示意:
... [k-1 ] [ k ] [k+1] ... // 删之前 即“先状态”
... [dele] [ 0 ] [ 1 ] ... // 删之后 即“后状态”
那么若要从n-1个人的“后状态”恢复到“先状态”,其实所有人的编号相当于加k,亦即f[n] = f[n-1] + k,再由于是环的问题,所以加完再对"先状态时,即删前"人数n取模即可。
这里有个小地方要注意一下,每次取模的是当前游戏人数,可以稍微想想为啥?如果你写成迭代版本,以i为下标迭代,F[1]=0;F[i]=(F[i-1]+k)%i;这里是模i而非n,要留意。