题目详情:
有n个人编号1-n,按照顺时针方向围成一个圆圈。它们预先定义好两个整数x,y。先从1号顺时针方向开始报数,报到x的人出圈,再从x的逆时针方向的后一个人从1开始报数,报到y的人出圈,再从这个人的顺时针方向后一个人开始从1报数,报到x的人出圈,如此反复,直到最后剩下一个人为止,问最后剩下的那个人是几号?
比如n = 10, x = 3, y = 2,报数的过程如下
报数人 1, 2, 3 3出圈
报数人 2,1 1出圈
接着2,4 ,5 5出圈
接着4,2 2出圈
接着4,6,7 7出圈
接着6,4 4出圈
接着6,8,9 9出圈
接着8,6 6出圈
接着8,10,8 8出圈
剩余 10号。
输入n,x,y输出剩余的编号。
数据范围 1 < n <= 1000000, 1<=x,y<=1000000000。
----------------------------------------------------------------------------------------------
这道题是约瑟夫斯问题,自己可以先百度下约瑟夫斯问题。
我的解法是:
(1)每消去一个人后,将圆圈内的人重新按先后顺序依次编号(为了便于计算,我从0开始编号);例如0,1,2,3,4,5中消去2,那么剩余的0,1,3,4,5重新编号为0,1,2,3,4;
(2)每消去一个人时,记录下这个被消去人在当前圈中的索引;
(3)当圆圈内剩余一个人时结束,此时这个人在当前圈中的索引为LastID=0,其实问题也就是找这个人在原始圈中的相应索引LastID;
(4)现在我们将最后一个被消去的人的索引插入到这个只有一个人的圈中,如果插入的索引值小于等于LastID(即在最后剩下人的前面),那么LastID加1;然后依次将倒数第二个、倒数第三个......消去人的索引插入到圈中,最后的LastID就是最后剩下人在原始圈中相应的索引。
代码如下:
#include <stdio.h>
#include <iostream>
#include <string>
#include <ctime>
#include <cstdlib>
#include <stack>
using namespace std;
class Test {
public:
static int remain (int n,int x,int y){
//题目的索引从1开始,我在计算过程中从0开始
int res=0;
int *delInd=new int[n-1];
int start=0;//保存的是下一次消去时,开始的位置
int flag=0;
int j=0;
for(int i=n;i>=2;--i){
if(0==flag%2){
delInd[j]=(start+(x-1)%i)%i;
start=(delInd[j]+i-1-1)%(i-1);
}else{
delInd[j]=(start+i-(y-1)%i)%i;
start=delInd[j]%(i-1);
}
++j;
++flag;
}
for(int i=n-2;i>=0;--i)
if(delInd[i]<=res)++res;
return res+1;
}
};
//start 提示:自动阅卷起始唯一标识,请勿删除或增加。
int main(){
clock_t t1=clock();
cout<<Test::remain(10,3,2)<<endl;
cout<<Test::remain(9,4,3)<<endl;
cout<<Test::remain(20000,4,3)<<endl;
clock_t t2=clock();
std::cout<<(t2-t1)/(double)CLOCKS_PER_SEC<<std::endl;
}
//end //提示:自动阅卷结束唯一标识,请勿删除或增加。
显然时间复杂度和空间复杂度都是O(n);