问题描述:有n个人围成一圈,分别编号为1,2,3,…,n-1,n,从1号开始报数,报到m的人出局,接着下一个人从1开始重新报数,再次报到m的人同样出局,如此循环,直到圈中只剩一个人为止,则此人为胜出者,求胜出者编号?
这是一个实际问题,可以用模拟报数过程方式来计算结果,但其时间复杂度过高,为O(m*n)。当然我们能够感觉到此问题应该可以用数学方式来解决,下面就让我们来看看如何用数学方式更加高效地解决此问题。
由于计算需要,不失本质地改动一下问题描述。n个人的编号为0,1,…,n-1,每次报数从0开始,报到m-1出局。先看第一个出局的人,其编号为(m-1)%n,这个人出局之后我们再也不需要考虑他了。这时剩下n-1个人,构成一个新的环,从编号为(k=m%n)的人开始报数。即k,k+1,…,n-2,n-1,0,1,2,…,k-3,k-2,而k开始报0。
我们现在做一下编号对应变换:
(1)k k+1 k+2 … … k-3 k-2(编号为k的人开始报0)
| | | | |
(2)0 1 2 … … n-3 n-2(编号为0的人开始报0)
(1)、(2)两式具有固定的对应关系,即如果(2)中编号为x的人最后胜出,那么就是(1)中编号为x~=(x+k)%n=(x+m%n)%n=(x+m)%n的人最后胜出。其实,式(2)就是由n-1人个人围成一圈的约瑟夫环,问题又回到原本,但规模却减一。如此看来,只要我们求出了问题规模为n-1的约瑟夫环,也就求出了问题规模为n的约瑟夫环。而n-1规模又可以由n-2规模求出,这就变成了一个递推问题。
递推公式:
设w(i)为i个人玩的的报到m出局的约瑟夫环的胜出者编号。
w(1)=0;
w(i)=(w(i-1)+m)%i; (i>1)
由于最终只需要w(n)的结果,而不需要保存中间结果,且如果编号是从1开始的,那么结果加一即可,故程序代码如下:
#include<iostream>
using namespace std;
int main()
{
int n,m;
while(cin>>n&&n!=0){
cin>>m;
int w=0;//胜出者编号
for(int i=2;i<=n;i++){
w=(w+m)%i;
}
cout<<w+1<<endl;
}
system("pause");
return 0;
}
其时间复杂度为O(n)。样例输入:
1 10
8 5
6 6
0
样例输出:
1
3
4