n个人排成一圈,按顺时针方向依次编号1,2,3…n。从编号为1的人开始顺时针"一二"报数,报到2的人退出圈子。这样不断循环下去,圈子里的人将不断减少。最终一定会剩下一个人。试问最后剩下的人的编号。
n很大,直接模拟题意不行,请找出规律。
n个人排成一圈,按顺时针方向依次编号1,2,3…n。从编号为1的人开始顺时针"一二"报数,报到2的人退出圈子。这样不断循环下去,圈子里的人将不断减少。最终一定会剩下一个人。试问最后剩下的人的编号。
n很大,直接模拟题意不行,请找出规律。
不超过1000组数据。
每组数据一行,每行一个正整数,代表人数n。 (1 <= n < 231)
每组输入数据输出一行, 仅包含一个整数,代表最后剩下的人的编号。
7
2
7
1
#include
#include
#include
using namespace std;
int main()
{
int n,a[1005];
while(scanf("%d",&n)==1)
{
int flag=1,i=1;
int nn=n;
n--;
memset(a,0,sizeof(a));
while(n)
{
if(a[i]==0)
{
if(flag==1)
{
i++;
if(i>nn)
i=1;
flag=2;
}
else
{
a[i]=1;
flag=1;
i++;
if(i>nn)
i=1;
n--;
}
}
else
{
i++;
if(i>nn)
i=1;
}
}
for(int i=1;i<=nn;i++)
if(a[i]==0)
printf("%d\n",i);
}
return 0;
}
利用递推可以发现规律
f(1)=1;
N为偶数:f(N)=2*f(N/2)+1;
N为奇数:f(N)=2*f((N-1)/2)+1;
f(n)表示的是n个人最后留下的人的序号
int f(int n)
{
if(n==1)
return 1;
else if(n&1)
return 2*f1((n-1)/2)+1;
else
return 2*f1(n/2)-1;
}
如果N=2^m+L,(0<=L<2^m),那么f(N)=2*L+1;
int f(int n)
{
int pos=1;
int temp=1;
for(int i=0; i<31; i++)
{
if(n&pos)// if(n<=pos)
temp=pos;// 得到不大于n的2^temp次幂
pos=(pos<<1); //pos*=2;
}
return ((n-temp)<<1)+1;
}
题目中的数数‘一二’,换成了数m个数
n个人排成一圈,按顺时针方向依次编号1,2,3…n。从编号为1的人开始顺时针"一二三...."报数,报到m的人退出圈子。这样不断循环
下去,圈子里的人将不断减少。最终一定会剩下一个人。试问最后剩下的人的编号。
本题的数据规模更具有挑战性,尝试更通用且高效的算法。
超过1000组数据。
每组数据一行,每行两个正整数,代表人数n (1 <= n < 231)和m(1<=m<=100)。
利用数学归纳可以得到:
f(1) = 0;
f(n) = (f(n - 1) + m) % n;
再优化一下
代码来自:https://blog.csdn.net/feng_zhiyu/article/details/73431094
#include
#include
using namespace std;
typedef long long LL;
LL solve(LL n,LL m,LL s)
{
if(m==1) return (n-1+s)%n;
LL ans=0,i=2;
while(i<=n)
{
if(ans+mn)
{
ans+=(n+1-i)*m;
break;
}
i+=x;
ans+=x*m;
}
else ///ans不是递增时
{
ans=(ans+m)%i;///普通求法
i++;
}
}
return (ans+s)%n;
}
int main()
{
LL n,m;
while(scanf("%lld%lld",&n,&m)!=EOF)
{
LL ans=solve(n,m,1);
printf("%lld\n",ans?ans:n);
}
return 0;
}