题目描述
n个人排成一圈,按顺时针方向依次编号1,2,3…n。从编号为1的人开始顺时针"一二三…"报数,报到m的人退出圈子。这样不断循环下去,圈子里的人将不断减少。最终一定会剩下一个人。试问最后剩下的人的编号。
本题的数据规模更具有挑战性,尝试更通用且高效的算法。
输入
不超过1000组数据。
每组数据一行,每行两个正整数,代表人数n (1 <= n < 231)和m(1<=m<=1000)。
输出
每组输入数据输出一行, 仅包含一个整数,代表最后剩下的人的编号。
样例输入 Copy
7 2
2 2
样例输出 Copy
7
1
约瑟夫问题公式推导过程
我们已经知道有一个约瑟夫问题求解公式a=(a+m)%k(a是出去的编号,m是报数号,k是上面报数后剩下的人数;注意,这里标号是从0开始的,最后结果要加1)
接下来是算法再优化:
可以发现,当a+m<k时,得出来的a=(a+m)%k还是a+m(比如(1+2)%10=(1+2)),继续(a+m)+m%(k+1)【为什么是k+1呢?看这个
我们的k就相当于上面代码的i,m相当于s=(s+2)%i中的2,只是这个是1,2报数】,只要%左边小于右边,算出来的数与左边相等,依此类推,我们可以扩展为x个m相加,有a+mx<k+(x-1),解出x<(k-1-a)/(m-1),当能整除时,取x=(k-1-a)/(m-1)-1,不能整除时取x=(k-1-a)/(m-1)【取整数部分,满足该不等式】,那么什么时候退出循环?当k+x>n时即可退出【x是报的m的个数,报到一个m k就加1】因为k+x>n,x和n都为整数,且x是1,2,3,……这样依次加1递增的,所以就有k+x-1=n,得出x=n-(k-1),带入公式a=(a+mx)%n【为什么这个时候是对n取余了呢?因为此时循环到达最后了,人数为n】得a=(a+(n-(k-1))*m)%n,退出;
当a+m>k时,按原公式a=(a+m)%k求解
【当时我理解这个东西理解了好久啊,最后在npy的帮助讲解下,终于懂了那么一丢丢,在这里感谢一下ta喽,虽然可能看不见,哈哈哈哈,我写的可能不是很清楚,仅供参考,欢迎批评指正~~~~~ _】
代码如下:
#include<bits/stdc++.h>
using namespace std;
int main(){
long long m,n;
while(scanf("%lld%lld",&n,&m)!=EOF){
long long a=0,k,x;
if(m==1){
cout<<n<<endl;
continue;
}
else{
for(k=2;k<=n;){
if(a+m<k){//当a+m<k时可以减少时间复杂度
if((k-1-a)%(m-1)==0){//x不能是m-1的倍数
x=(k-1-a)/(m-1)-1;
}
else x=(k-1-a)/(m-1);//a+m*x<k+x-1变换而来
if(k+x>n){//k+x>n ---->k+x-1==n---->x=n-(k-1)
a=(a+(n-(k-1))*m)%n;
break;//当还有n个人时的情况已找到,退出循环
}
k=k+x;
a=(a+x*m)%k;
}
else{
a=(a+m)%k;
k++;
}
} }
cout<<a+1<<endl;
}
return 0;
}