n个人想玩残酷的死亡游戏,游戏规则如下:
n个人进行编号,分别从1到n,排成一个圈,顺时针从1开始数到m,数到m的人被杀,剩下的人继续游戏,活到最后的一个人是胜利者。
请输出最后一个人的编号。
Input
输入n和m值。m>1。
Output
输出胜利者的编号。
Sample Input
5 3
Sample Output
4
Hint
第一轮:3被杀第二轮:1被杀第三轮:5被杀第四轮:2被杀
- 比较普遍的解决方案是利用循环链表进行模拟
- (1)直接建立循环链表
#include <iostream>
#include <cstring>
using namespace std; //使用名称空间std
//链表法
struct Lnode
{
int no;
Lnode* pre;
Lnode* next;
};
typedef struct
{
Lnode* befo;
Lnode* rear;
}DLinklist;
int main()
{
DLinklist kill; //自杀环
int n, m; //n人循环m
int i;
while (cin >> n >> m)
{
kill.befo = (Lnode*)new Lnode;
kill.rear = kill.befo;
kill.rear->no = 1;
i = 2;
while (i <= n)
{
kill.rear->next = (Lnode*) new Lnode;
Lnode* p = kill.rear->next;
p->no = i++;
p->pre = kill.rear;
p->next = kill.befo;
kill.rear = p;
}
kill.befo->pre = kill.rear;
//约瑟夫环初始化完成
int flag = 1; //计数器
Lnode* t, *j; //工作指针
t = kill.befo;
while (flag < n)
{
for (i = 1;i < m;i++)
t = t->next; //t为被杀者
j = t;
t = t->next; //此时逻辑关系:j->pre j t
t->pre = j->pre;
j->pre->next = t; //删除被杀者
delete j;
flag++;
}
cout << t->no << endl;
delete t;
}
return 0;
}
(2)用数组模拟循环链表
#include <iostream>
#include <cstring>
using namespace std; //使用名称空间std
int main()
{
int pre[100], next[100];
int i, t;
int n, m;
int flag; //计数器
while (cin >> n >> m)
{
flag = 1;
for (i = 0;i < n;i++)
next[i] = (i + 1) % n;
for (i = 1;i < n;i++)
pre[i] = (i - 1) % n;
pre[0] = n - 1;
//循环链表初始化完毕
i = 0;
while (flag < n)
{
for (t=1;t <=m - 1;t++)
i = next[i];
t = i;
i = next[i];
pre[next[t]] = pre[t];
next[pre[t]] = next[t]; //删除结点
flag++;
}
cout << i+1 << endl; //逻辑次序从0开始,实际编号从1开始
}
return 0;
}
- 利用数学递归法
初始情况: 0, 1, 2 …n-2, n-1 (共n个人)
第一个人(编号一定是(m-1)%n,设之为(k-1) ,读者可以分m<n和m>=n的情况分别试下,就可以得出结论) 出列之后,
剩下的n-1个人组成了一个新的约瑟夫环(以编号为k==m%n的人开始):
k k+1 k+2 … n-2, n-1, 0, 1, 2, …,k-3, k-2
现在我们把他们的编号做一下转换:
old -> new
k --> 0
k+1 --> 1
k+2 --> 2
…
…
k-2 --> n-2
k-1 --> n-1
变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解:例如new是最终的胜利者,那么根据上面这个表把这个new变回去不刚好就是n个人情况的解吗!
new ->old?(这正是从n-1时的结果反过来推n个人时的编号!)
0 -> k
1 -> k+1
2 -> k+2
…
…
n-2 -> k-2
变回去的公式 old=(new+k)%n
那么,如何知道(n-1)个人报数的问题的解?只要知道(n-2)个人的解就行了。(n-2)个人的解呢?只要知道(n-3)的情况就可以了 ---- 这显然就是一个递归问题:
令f[i]表示i个人玩游戏报m退出最后胜利者的编号,最后的结果就是f[n]
递推公式
f[1]=0;
f[i] = (f[i-1] + m) % i ; (i>1)
本文来自 d4shman 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/wusuopubupt/article/details/18214999?utm_source=copy
(1)递归法(反向推)
#include <iostream>
#include <cstring>
using namespace std; //使用名称空间std
//递推法
int main()
{
int n, m;
int f(int n, int m);
while (cin >> n >> m)
{
cout << f(n, m)+1 << endl; //逻辑上由0-(n-1),实际是1-n
}
return 0;
}
int f(int n,int m)
{//每个环对应一个新环,每个新环使用一次后又对应下一个新环
if (n == 1) //最后一个新环对应的幸存者序号
return 0;
else
return (f(n - 1, m) + m ) % n; //old=(new+m)%n
(2)递推法(正向推)
#include <iostream>
#include <cstring>
using namespace std; //使用名称空间std
int main()
{//递推
int _new;
int n, m;
while (cin >> n >> m)
{
_new = 0; //最后一个新环(只有一人)的序号为0
for (int i = 2;i <= n;i++) //倒数第二个新环——倒数第n-1个新环
_new = (_new + m) % i; //old=(new+m)%n;
cout << _new + 1 << endl; //实际编号=逻辑编号加一
}
return 0;
}