一群小孩围成一圈,任意假定一个数N,从第一个小孩起,逆时针方向数,每数到第M个小孩时,该小孩就离开,然后再由下一个重新报数,小孩不断离开,圈子不断缩小。最后,剩下的一个小孩便是胜利者。究竟胜利者是第几个小孩呢?
类似这样的问题都叫Josephus(约瑟夫斯)问题。我们可以用一个循环链表表示排成圆圈的人,整数i代表第i个人,先为1号建立一个单节点循环链表,再把2号到N号依次插入到1号节点后面,这样就形成了一个由1--N(逆时针)的员,最后x指向N,然后我们从1号开始跳过M-1个节点,把第M-1个节点的链接改为指向第M+1号节点,一直这样做,直到只剩下一个节点为止。
#include<iostream> using namespace std; typedef int T; struct node { T data; struct node *next; node(T x,node *t):data(x),next(t) { } }; typedef struct node *Link; int main() { int i, N,M; cin>>N>>M; Link t=new node(1,0); t->next=t; Link x=t; for(i=2;i<=N;i++) x=(x->next=new node(i,t)); while(x!=x->next) { for(i=1;i<M;i++) x=x->next; x->next=x->next->next; } cout<<x->data<<endl; }
递推思路:
本思路可用O(n)的复杂度解决最原始的Josephus问题,即只找出最后一个幸免者。关于约瑟夫(Josephus)问题:
N个人围成一圈,从第一个开始报数,第M个将被杀掉,最后剩下一个,其余人都将被杀掉。例如N=6,M=5,被杀掉的人的序号为5,4,6,2,3。最后剩下1号。
为了方便使用mod处理数据,将N个人编号0-N-1,每次报数从0开始,报到M-1的被杀掉。
第一个被杀掉的一定是编号(M-1)mod(N)的人,他被杀掉后,令k=(M)mod(N),则剩下的问题为:编号为:k,k+1,k+2,…,n-1,0,1,…,k-2的N-1个人,从k开始从0进行报数,报到M-1的人被杀掉。所以如果做一个i’ = (i-k)mod(N)的映射,就可以真正将问题转换成J(N-1,M),解出答案后再做逆映射i = (i’+k)mod(N)即可得到J(N,M)的解,即J(N,M) = (J(N-1,M)+k)mod(N)。又知道k = (M)mod(N),所以递推公式的形式是J(N,M) = (J(N-1,M)+M)mod(N)。递推起点是J(1,M)=0。(注意k,k+1,k+2,…n-1,0,1,…k-2的序列在考虑mod操作后并没有数据上的跳跃,因此做了映射后才仍然是问题的原型,抹去了杀掉一个人的影响。)
J(1,M) = 0 J(N,M) = (J(N-1,M)+M)mod(N)
这样,只要从1推到N就可以得出问题的答案,复杂度O(N)。
深入:
http://blog.pfan.cn/xiangyu/3527.html
用数学方法解的时候需要注意应当从0开始编号,因为取余会等到0解。
实质是一个递推,n个人中最终存活下来的序号与n-1个人中存活的人的序号有一个递推关系式。
分析:
假设除去第k个人。
0, 1, 2, 3, ..., k-2, k-1, k, ..., n-1 //original sequence (1)
0, 1, 2, 3, ..., k-2, , k, ..., n-1 //get rid of kth person (2)
k, k+1, ..., n-1, 0, 1, ..., k-2 //rearrange the sequence (3)
0, 1, ..., n-k-1, n-k, n-k+1, ..., n-2 //the n-1 person (4)
我们假设f(n)的值为n个人中最后存活的人的序号,则
注意到(2)式(3)式(4)式其实是同一个序列。
注意(1)式和(4)式,是同一个问题,不同的仅仅是人数。
假设我们已知f(n-1),即(4)式中最后剩下的人的序号,则(3)式所对应的序号,就是f(n),即(1)式n个人中最后存活的序号。
而从(3)(4)式中我们不难发现有这样一个递推式:
f(n) = (f(n-1) + k) % n
显然,f(1) = 0。
于是递推得f(n)
#include <stdio.h> int main() { int n, k, m, i, x; while (scanf("%d%d%d", &n, &k, &m) != EOF) { if (n==0 && k==0 && m==0) break; x = 0; for (i=2; i!=n; ++i) x = (x + k) % i; x = (x + m) % i + 1; printf("%d\n", x); } return 0; }