按照如下规则去杀人:
所有人围成一圈
顺时针报数,每次报到q的人将被杀掉
被杀掉的人将从房间内被移走
然后从被杀掉的下一个人重新报数,继续报q,再清除,直到剩余一人
你要做的是:当你在这一群人之间时,你必须选择一个位置以使得你变成那剩余的最后一人,也就是活下来。
1.思路:注意这里的前提是n = 2^k(也就是2的幂次个人,其他数另外讨论)
如果只有2个人,显然剩余的为1号
如果有4个人,第一轮除掉2,4,剩下1,3,3死,留下1
如果是8个人,先除去2,4,6,8,之后3,7,剩下1,5,除去5,又剩下1了
定义J(n)为n个人构成的约瑟夫环最后结果,则有j(2^k) = 1
J(n) = 2^k – 2^k-1 = 2^k-1 n=2^k
J(n) = 2^k-1 – 2^k-2 = 2^k-2 n=2^k-1
………
J(2^2) = 2^2 – 2^1 = 2^1 n=2^2
递推得到如上结果,起始我们仔细分析也就是每次除去一半的元素,然后剩余的一半继续重复之前的策略,再除去一半。(可想到递归)
结合:J(2) = 1 我知道两个数,从1开始,肯定是2先死,剩下1.
得到:j(2^k) = 1
2.but当n 不等于 2^k时候,比如9,11这种:
n 不等于 2^k时,就不存在这样的easy的规律了,重新分析:
假设n = 9,这时候如图下:
能看出来,我们干掉第一个人也就是2,之后就只剩下8个人了,又回到J(2^k)上了,这时候我们需要的是找到当前的1号元素。
见图下:
这时候,我们从3号开始,就成了另外一个规模小1的约瑟夫问题(恰好为2^k的特例)。
这时候,我们可以把3号看成新的约瑟夫问题中的1号位置:
J(8) = J(2^3) = 1,也就是说这里的1代表的就是上一个问题中的3号
So:J(9) = 3
答案为3号
同理可知所有的非2^k的数都是这样:
假设n = 2^k + t,t可以随意取,比如1,2,3…….
假设n = 11,这时候n = 2^3 + 3,也就是说t = 3,所以开始剔除元素直到其成为2^k问题的约瑟夫问题。
So,我们在剔除了t(3)个元素之后(分别是2,4,6),此时我们定格在2t+1(7)处,并且将2t+1(7)作为新的一号,而且这时候的约瑟夫环只剩下2^3,也就是J(2^3 + 3) = 2*3 + 1 = 7,答案为7
总结一下这个规律:
J(2^k + t) = 2t+1
3.说完了特例,现在说说q 不等于2的情况下:
当q ≠ 2:
我们假定:
- n — n人构成的约瑟夫环
- q — 每次移除第q个人
约定:
- Jq(n)表示n人构成的约瑟夫环,每次移除第q个人的解
- n个人的编号从0开始至n-1
我们沿用之前特例的思想:能不能由Jq(n+1)的问题缩小成为J(n)的问题(这里的n是n+1规模的约瑟夫问题消除一个元素之后的答案),Jq(n)是在Jq(n+1)基础上移除一个人之后的解。也就是说,我们能由Jq(n)得到Jq(n+1)。
规律:Jq(n+1) = ( Jq(n) + q ) / (n+1)
详细推导过程见这篇博文
大致是如下这样:
0 1 2 3 4 5 …… n-1 总共n人
设第q个人也就是下标为q-1的那位,杀死:
剩下n-1个人,如下:
q q+1 q+2 …… n-2 n-1 0 1 2 …… q-2 (这里是除去q-1这位兄台的剩余n-1人)
这时,又来重复我们的老套路:将新的被杀的后一个人作为新的0号,于是新的如下:
0 1 2 …… ………. …….. n-2
1
2
3
4
5
6
7
8
9
其实就是从q开始,到之前最大数n-1,每个数都减去q,从0开始之后接着n-1这个新的值每次往后加1,直到加到n-1(这个下标)
举个例子:
J4(9) :
0 1 2 3 4 5 6 7 8 消去3–> 0 1 2 4 5 6 7 8( 0 1 2)
对应的新值: 0 1 2 3 4 5 6 7
其中:q=4,从3之后第一个数4开始:每个数5-q=1,6-q=2,7-q=3,8-q=4,因为是个环,0-q=-4,1-q=-3 ….直到加到n-1=7
这就相当于一个限定范围内的数的相对位置,-1代表的是最后一个元素,也就是之前的8
(-2就代表7,-3代表6,-4代表5…..-9代表0,从后面开始数过来第9位)
1
2
3
4
5
6
7
8
9
大致过程如图下:
这里写图片描述
那么我们知道是这么得到的新的队列,那么也很容易知道怎么反推了:
反观如上的变化情况,都是减去一个q,所以:
变回去的公式如下:old = (new + q) % n
这里的old和new指的是下标,n指的是总共有多少人
知道了怎么推出之前的下标,那么也就可以一步步递推回去得到开始的队列或者从小推到大得到最后剩余的结果。
最后再做一道实际点的例子,求J2(4):
J2(1) = 0
J2(2) = (J2(1) + 2) % 2 = 0
J2(3) = (J2(2) + 2) % 3 = 2
J2(4) = (J2(3) + 2) % 4 = 0
……..
这样一步步求就能得到所有的给出n和q条件的答案了。
#include<iostream>
#include<stdio.h>
using namespace std;
int yuesefu(int n,int m){
if(n == 1){
return 0; //这里返回下标,从0开始,只有一个元素就是剩余的元素0
}
else{
return (yuesefu(n-1,m) + m) % n; //我们传入的n是总共多少个数
}
}
int main(void){
int a,b;
cin>>a>>b;
cout<<yuesefu(a,b)<<endl;
//或者,直接循环迭代,求出来的result如上
int result = 0;
for(int i = 2;i <= a;i++){
result = (result+b) %i;
}
cout<<"result = "<<result<<endl;
return 0;
}
循环链表模拟代码
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<stack>
#include<vector>
using namespace std;
typedef struct node
{
int number;
struct node *next;
}Node;
Node* CreatNode(int x)//创建链表节点的函数
{
Node *p;
p=(Node*)malloc(sizeof(Node));
p->number=x;
p->next=NULL;
return p;
}
Node* CreatJoseph(int n)
{
Node *head,*p,*q;
for(int i=1;i<=n;i++)
{
p=CreatNode(i);
if(i==1)
head=p;
else
q->next=p;
q=p;
}
q->next=head;
return head;
}
void RunJoseph(int n,int m)//模拟运行约瑟夫环,每数到一个数,将它从环形链表中摘除,并打印出来
{
Node *p,*q;
p=CreatJoseph(n);
int i;
while(p->next!=p)
{
for(int i=1;i<m-1;i++)
{
p=p->next;
}
q=p->next;
p->next=q->next;
p=p->next;
// printf("%d--",q->number);
free(q);
}
printf("%d\n",p->number);
}
int main()
{
int n,m;
scanf("%d %d",&n,&m);
RunJoseph(n,m);
return 0;
}