题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5643
题意:
有n个人按顺序逆时针围成一个圈1,2,3,...,n。一轮第一个人从 1 开始报数,报到 1 就停止且报到1的这个人出局。
第二轮从上一轮出局的人的下一个人开始从1报数,报到2就停止且报到2的这个人出局。
第三轮从上一轮出局的人的下一个人开始从1报数,报到3就停止且报到3的这个人出局。
第 n - 1轮从上一轮出局的人的下一个人开始从 1报数,报到 n - 1就停止且报到 n - 1的这个人出局。
最后剩余的人是幸存者,请问这个人的标号是多少?
思路:
我们来考虑简单版的约瑟夫环问题,n个人站成圈,报到k的人出去,最后剩下的人的编号。
它的程序可以这么写。
int cal(int n,int k)
{
int ans = 0;
for(int i = 2; i <= n; i++ ) ans = ( ans + k ) % i;
return ans + 1; //设定编号为0~n-1 所以最后要+1恢复
}
有n个人编号0~n-1 那么下一轮开始的时候就是当前编号k的人第一个开始报数,我们将其重新编一下号,发现:
n个人的情况:
(k-1)淘汰 k(下一轮开始报数)
n-1个人的情况:
0(开始报数,同时也是上一轮编号为k的位置)
每一轮出去一个人,我们就将所有人重新编号,第一个开始报数的人就编为0,所以上一把编号为k的人,这一把编号为0,以此类推,那么这一把编号为x的人,上一把编号就为(x+k)%i,i是上一把的总人数。既然如此,最后一把剩余的人的编号一定为0,那么我们往前推n-1把就可以得到初始时(有n个人)他的编号是多少。
同理,我们只需要修改上面代码的一个地方就可以了,每一把出去的人报到的数不同,而且是一个倒推过程,所以最后一次报n-1,倒数第二次报n-2...
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <stack>
#include <map>
#include <set>
#include <vector>
#include <sstream>
#include <queue>
#include <utility>
using namespace std;
#define rep(i,j,k) for (int i=j;i<=k;i++)
#define Rrep(i,j,k) for (int i=j;i>=k;i--)
#define Clean(x,y) memset(x,y,sizeof(x))
#define LL long long
#define ULL unsigned long long
#define inf 0x7fffffff
#define mod %100000007
int T,n;
int ans[5009];
int cal(int x)
{
int ans = 0;
rep(i,2,x) ans = (ans+x+1-i) % i;
return ans+1;
}
void init()
{
ans[1] = 1;
ans[2] = 2;
//rep(i,1,15) cout<<i<<" : "<<cal(i)<<endl;
rep(i,3,5000) ans[i] = cal(i);
}
int main()
{
init();
cin>>T;
while(T--)
{
scanf("%d",&n);
printf("%d\n",ans[n]);
}
return 0;
}