1 问题描述
约瑟夫斯是公元1世纪的犹太历史学家,他领导了反抗罗马人的武装起义,但是失败了。他和四十名犹太士兵被罗马人围困在一个山洞中。这四十个士兵宁死不屈,决定杀身成仁。但约瑟夫斯不想,
但又不便公开反对,于是提出一个方法,就是四十一个人站成一个圈,从某人开始数起,凡数到三的人就让大家成全他升天,这样下去直到剩下最后一个人,这个人就自杀。大家都没有意见,于是约瑟夫斯就挑选了第31号的位置。结果所有人都死了,剩下他一个活下来投降了罗马人。这也是约瑟夫斯问题的最初提法。
但现在我们不想知道最后还在队列的是谁,我们想知道每次出队列的是谁,以及他们的顺序。
现在,有n个人围成一圈,从第一个人开始报数,数到 m的人出列,再由下一个人重新从 1开始报数,数到 m的人再出圈,依次类推,直到所有的人都出圈,请输出依次出圈人的编号。
输入描述
输入两个整数 n,m。n表示总人数,m表示数到m的人就出列
输出描述
输出一行 n 个整数,按顺序输出每个出圈人的编号。
n的区间为[1,1000]
2 解决
(1)数组
- 这个之前写过了一遍,直接建立一个数组,0号位不存,直接赋值为0,从1开始,和人的顺序对应,tag[i]存储的就是i,也就是这个人的序号,出列以后赋值为0,表示出去了
- 或者新建一个数组初始化为都是1,出列了就记为0,因为下标从1开始,每个数的下标就是该位置人的序号,不必遍历一遍数组为其赋值,减少操作,但是一开始就要为数组分配好空间
- 代码
#include<iostream>
#include<cstdlib>
#include<cstdio>
using namespace std;
int main(int argc,char *argv[]){
//freopen("file in.txt","r",stdin);
int n,m;
cin>>n>>m;
int i;
int index=0; //下标
int flag; //为了不改变n的值
int *tag;
tag = (int*)malloc(sizeof(int)*(n+1));
tag[0]=0;
// tag作为这个位置的人是否已经出列的标志,同时他的值是这个位置的人的序号
for(i=1;i<=n;i++){
tag[i]=i;
}
flag=n;
while(flag){
for(i=1;i<=m;i++){
index++;
if(index>n) //到达末尾,手动调到开头
index=1;
while(!tag[index]){
index++;
if(index>n)
index=1;
}
}
cout<<tag[index]<<endl;
tag[index]=0; //说明已经出列
flag--;
}
return 0;
}
- 很明显,这个题目没有这么简单,所以我最后几个用例TLE了
(2) 链表
- 既然每次出列的人的位置都要遍历并且判断,显然是多余的操作,那么我直接使用链表来操作看看
- 建立一个循环链表,每次出去一个人去掉这个节点
- 代码
#include<iostream>
#include<cstdlib>
#include<cstdio>
using namespace std;
struct listnode{
int order;
struct listnode *next;
};
typedef struct listnode Node;
void yoseph(int n, int m){
int i;
Node *head;
Node *p;
Node *temp;
head = (Node*)malloc(sizeof(Node));
if(!head){
exit(0);
}
head->order=1;
head->next=NULL;
p=head;
for(i=2;i<=n;i++){
temp = (Node*)malloc(sizeof(Node));
if(!temp)
exit(0);
// 尾插法
temp->order=i;
temp->next=NULL;
p->next=temp;
p=temp;
}
p->next=head; //形成封闭链表
p=head;
temp=p; //防止使用未初始化的指针
while(p->next!=p){ //只剩下一个节点的时候退出循环,然后最后单独输出
for(i=1;i<m;i++){
temp = p;
p = p->next;
}
cout<<p->order<<endl;
temp->next = p->next; //删除这个节点
p = p->next;
}
cout<<p->order<<endl;
}
int main(int argc,char *argv[]){
//freopen("file in.txt","r",stdin);
int n,m;
cin>>n>>m;
yoseph(n,m);
return 0;
}
- 很悲伤
,我还是没有AC,还是提示TLE
- 那要这么办呢,递归肯定是不行的吧,不管怎么样,还是试一试吧
(3)递归(最终版)
- 这个方法的思想是总结第i次出列的人与第i-1次出列的人具有什么关系
- 复习
- 假设n=8,m=3,从第一个数开始报数
- %是为了防止越界,由于是递归操作,每次传进去的n可是不一样的
- 代码
// 第i个出列的人和第i-1出列的人有什么关系
// 下标从零开始
#include<iostream>
#include<cstdlib>
#include<cstdio>
using namespace std;
int yoseph(int n, int m,int i){
if(i==1)
return (m-1)%n;
return (yoseph(n-1,m,i-1)+m)%n;
}
int main(int argc,char *argv[]){
//freopen("file in.txt","r",stdin);
int n,m;
int i;
cin>>n>>m;
for(i=1;i<=n;i++){ //第i个出列的人
cout<<yoseph(n,m,i)+1<<endl;
}
return 0;
}
- 没想到竟然过了,还以为递归肯定过不了
4 总结
- 考虑一种情况,假如m非常大的话,前两种解法就会一直在那里无效绕圈,而递归会直接通过计算给出答案
- 既然这样的话,链表改进一下应该是可以解决的,循环m个节点之前先对m进行取余操作