约瑟夫问题
这里补充一下约瑟夫问题的描述:N个人围成一圈,从第一个开始报数,数到M的人出队,然后他的下一位继续从1开始报数,数到M的出队,如此循环直到剩下一个人,求最后剩下的那个人最初是队伍中的第几位。
解决这道题可以采用模拟报数的方法,建立一个大小为N的数组,数组的第N个元素表示第N个人是否还在队伍中,首先将每个元素都置为1,表示全员都在队伍中。如果第N个人出队,则将第N个元素置为0。
模拟报数可以使用一个累加计数器,用它表示这轮报数已有多少人报数,然后循环访问每个人,若其在队伍中,则将计数器+1,如果累加到M,则这个人出队。如此循环,直到N-1个人出队,仅剩1人。
最后遍历一下那个数组,找到还在队伍中的人就可以。
代码如下:
#include using namespace std;int main(){int m, n, i, s = 0, rem;//s为计数器, rem为剩余人数int *a;cout > n >> m;rem = n;a = new int[n];for (i = 0; i 1){s += a[i];if (s == m)//第i个人出队,重置累加计数器{s = 0;rem--;a[i] = 0;}i++;i %= n;}for (i = 0; i < n; i++)if (a[i]){cout << "剩下的人为:" << i << endl;break;}delete []a;return 0;}
这个问题解答起来比较简单:敌人从1号开始,隔一个杀一个,第一圈把所有的奇数号码的战士圈杀光了。剩下的32名战士需要重新编号,而敌人在第二圈杀死的是重新编号的奇数号码。
由于第一圈剩下的全部是偶数号2,4,6,…,64。把它们全部用2去除,得1,2,3,…,32。这是第二圈编的号码。第二圈杀过以后,又把奇数号码都杀掉了,还剩16个人。如此下去,可以想到最后剩下的必然是64号。
64=26,它可以连续被2整除6次,是从1到64中能被2整除次数最多的数,因此,最后必然把64 号留下。
如果有65名战士被俘,敌人还是按上述的方法残杀战士,最后还会剩下约瑟夫斯吗?
经过计算,很容易得到结论,不是。因为第一个人被杀后,也就是1号被杀后,第二个被杀的是必然3号。如果把1号排除在外,那么还剩下的仍是64人,新1号就是3号。这样原来的2号就变成了新的64 号,所以剩下的必然是2号。
进一步的归类,不难发现如果原来有2k个人,最后剩下的必然2k号;如果原来有2k+1个人,最后剩下2号;如果原来有2k+2个人,最后剩下4号……如果原来有2k+m个人,最后剩下2m号.
比如:原来有100人,由于100=64+36=26+36,所以最后剩下的就是36×2=72号;又如:原来有111人,由于100=64+47=26+47,所以最后剩下的就是47×2=94号。
*问题分析与算法设计
约瑟夫问题并不难,但求解的方法很多;题目的变化形式也很多。这里给出一种实现方法。
题目中30个人围成一圈,因而启发我们用一个循环的链来表示。可以使用结构数组来构成一个循环链。结构中有两个成员,其一为指向下一个人的指针,以构成环形的链;其二为该人是否被扔下海的标记,为1表示还在船上。从第一个人开始对还未扔下海的人进行计数,每数到9时,将结构中的标记改为0,表示该人已被扔下海了。这样循环计数直到有15个人被扔下海为止。
一般形式
约瑟夫问题是个有名的问题:N个人围成一圈,从第一个开始报数,第M个将被杀掉,最后剩下一个,其余人都将被杀掉。例如N=6,M=5,被杀掉的人的序号为5,4,6,2,3。最后剩下1号。
假定在圈子里前K个为好人,后K个为坏人,你的任务是确定这样的最少M,使得所有的坏人在第一个好人之前被杀掉。
C++代码示例:
#include<iostream>
using namespace std;
void main()
{
int n,m,a[101],k,i,j,num; //计数器是从1开始的,所以100个人用101
cout<<"请输入参加游戏的玩家人数(不超过100人):";
cin>>n;
cout<<"----------------------------------------"<<endl;
if(n>100)
{
cout<<"玩家太多,请重新登陆此程序!"<<endl;
return;
}
cout<<"输入游戏中要玩的数字:";
cin>>m;
cout<<"----------------------------------------"<<endl;
for(i=1;i<=n;i++)
{
a【i】=1;//注意百度百科里不让使用ASCII里的方括号,这里是中文字符集里的方括号,
}
j=0;
k=0;
for(i=1;i<=n+1;i++){
if(a【i】==1){
j=j+a【i】;
if(j==m)
{
j=0;
a【i】=0;
k++;
}
if(k==n){
num=i;
break;
}
}
if(i==n+1)
i=0;
}
cout<<"最后获胜的玩家是第 "<<num<<" 号玩家!"<<endl;
cout<<"----------------------------------------"<<endl;
}
写完密码约瑟夫就想到原来看到约瑟夫问题的一个数学解法 很巧妙很简单 不过只能推出最后一个出列的人
无论是用链表实现还是用数组实现都有一个共同点:要模拟整个游戏过程,不仅程序写起来比较烦,而且时间复杂度高达O(nm),当n,m非常大(例如上百万,上千万)的时候,几乎是没有办法在短时间内出结果的。我们注意到原问题仅仅是要求出最后的胜利者的序号,而不是要读者模拟整个过程。因此如果要追求效率,就要打破常规,实施一点数学策略。
为了讨论方便,先把问题稍微改变一下,并不影响原意:
问题描述:n个人(编号0~(n-1)),从0开始报数,报到(m-1)的退出,剩下的人继续从0开始报数。求胜利者的编号。
我们知道第一个人(编号一定是m mod n-1) 出列之后,剩下的n-1个人组成了一个新的约瑟夫环(以编号为k=m mod n的人开始):
k k+1 k+2 ... n-2, n-1, 0, 1, 2, ... k-2
并且从k开始报0。
现在我们把他们的编号做一下转换:
k --> 0
k+1 --> 1
k+2 --> 2
...
...
k-2 --> n-2
k-1 --> n-1
变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解:例如x是最终的胜利者,那么根据上面这个表把这个x变回去不刚好就是n个人情况的解吗?!!变回去的公式很简单,相信大家都可以推出来:x'=(x+k) mod n
如何知道(n-1)个人报数的问题的解?对,只要知道(n-2)个人的解就行了。(n-2)个人的解呢?当然是先求(n-3)的情况 ---- 这显然就是一个倒推问题!好了,思路出来了,下面写递推公式:
令f表示i个人玩游戏报m退出最后胜利者的编号,最后的结果自然是f[n]
递推公式
f[1]=0;
f=(f+m) mod i; (i>1)
有了这个公式,我们要做的就是从1-n顺序算出f的数值,最后结果是f[n]。因为实际生活中编号总是从1开始,我们输出f[n]+1
由于是逐级递推,不需要保存每个f,程序也是异常简单:
c++
#include <stdio.h>
int main()
{
int n, m, i, s=0;
printf ("N M = "); scanf("%d%d", &n, &m);
for (i=2; i<=n; i++) s=(s+m)%i;
printf ("The winner is %d\n", s+1);
}
pascal
var n,m,i,s:integer;
begin
write('N M =');
read(n,m);
for i:=2 to n do
s:=(s+m) mod i;
writeln('The winner is ',s+1);
end.
这个算法的时间复杂度为O(n),相对于模拟算法已经有了很大的提高。算n,m等于一百万,一千万的情况不是问题了。可见,适当地运用数学策略,不仅可以让编程变得简单,而且往往会成倍地提高算法执行效率。
约瑟夫问题10e100版(from vijios)
描述 Description
n个人排成一圈。从某个人开始,按顺时针方向依次编号。从编号为1的人开始顺时针“一二一”报数,报到2的人退出圈子。这样不断循环下去,圈子里的人将不断减少。由于人的个数是有限的,因此最终会剩下一个人。试问最后剩下的人最开始的编号。
输入格式 Input Format
一个正整数n,表示人的个数。输入数据保证数字n不超过100位。
输出格式 Output Format
一个正整数。它表示经过“一二一”报数后最后剩下的人的编号。
样例输入 Sample Input
9
样例输出 Sample Output
3
时间限制 Time Limitation
各个测试点1s
注释 Hint
样例说明
当n=9时,退出圈子的人的编号依次为:
2 4 6 8 1 5 9 7
最后剩下的人编号为3
初见这道题,可能会想到模拟。可是数据实在太大啦!!
我们先拿手来算,可知n分别为1,2,3,4,5,6,7,8...时的结果是1,1,3,1,3,5,7,1...
有如下规律:从1到下一个1为一组,每一组中都是从1开始递增的奇数,且每组元素的个数分别为1,2,4...
这样就好弄了!!
大体思路如下:
①read(a)
②b:=1,c:=1{b为某一组的元素个数,c为现在所加到的数}
③while c<a do (b:=b*2,c:=b+c){超过目标时停止加数}
⑥c:=c-b{退到前一组}
⑦x:=a-c{算出目标为所在组的第几个元素}
⑧ans:=x*2-1{求出该元素}
⑨write(ans)
有了思路,再加上高精度就可以了。我写的代码比较猥琐,因为是先把上面的思路敲进去,再写过程,又把一些简单的过程合到主程序中了,所以有点乱,也有点猥琐。起提供思路的作用还是完全可以的吧~~~
var a,b,c:array[1..105]of integer;
la,lb,lc,i:integer;
s:string;
procedure incc;
var i:integer;
begin
for i:=1 to 105 do c:=c+b;
for i:=1 to 104 do if c>9 then
begin
c:=c+c div 10;
c:=c mod 10;
end;
end;
function cxiaoa:boolean;
var i:integer;
begin
cxiaoa:=false;
for i:=105 downto 1 do
if c<a then begin cxiaoa:=true;break;end
else if c>a then break;
end;
procedure doubleb;
var i:integer;
begin
for i:=1 to 105 do b:=b*2;
for i:=1 to 104 do if b>9 then
begin
b:=b+b div 10;
b:=b mod 10;
end;
end;
procedure decc;
var i,j:integer;
begin
for i:=1 to 104 do
if c>=b then c:=c-b else
begin
j:=i+1;
while c[j]=0 do inc(j);
while j>i do
begin
c[j]:=c[j]-1;
c[j-1]:=c[j-1]+10;
dec(j);
end;
c:=c-b;
end;
end;
procedure fua;
var i:integer;
begin
for i:=1 to 104 do
if a>c then a:=a-c else
begin
a:=a-1;
a:=a+10;
a:=a-c;
end;
end;
procedure outit;
var i,j:integer;
begin
for i:=1 to 105 do a:=a*2;
for i:=1 to 104 do if a>9 then
begin
a:=a+a div 10;
a:=a mod 10;
end;
if a[1]>0 then a[1]:=a[1]-1 else
begin
j:=2;
while a[j]=0 do inc(j);
while j>1 do
begin
a[j]:=a[j]-1;
a[j-1]:=a[j-1]+10;
dec(j);
end;
a[1]:=a[1]-1;
end;
for i:=105 downto 1 do if a>0 then begin j:=i;break;end;
for i:=j downto 1 do write(a);
end;
begin
readln(s);
la:=length(s);
for i:=la downto 1 do a:=ord(s[la+1-i])-ord('0');
b[1]:=1;
c[1]:=1;
while cxiaoa do
begin
doubleb;
incc;
end;
decc;
fua;
outit;
end.
笔算解决约瑟夫问题
在M比较小的时候 ,可以用笔算的方法求解,
M=2
即N个人围成一圈,1,2,1,2的报数,报到2就去死,直到只剩下一个人为止。
当N=2^k的时候,第一个报数的人就是最后一个死的,
对于任意的自然数N 都可以表示为N=2^k+t,其中t<n/2
于是当有t个人去死的时候,就只剩下2^k个人 ,这2^k个人中第一个报数的就是最后去死的。这2^k个人中第一个报数的人就是2t+1
于是就求出了当M=2时约瑟夫问题的解:
求出不大于N的最大的2的整数次幂,记为2^k,最后一个去死的人是2(N-2^k)+1
M=3
即N个人围成一圈,1,2,3,1,2,3的报数,报到3就去死,直到只剩下一个人为止。
此时要比M=2时要复杂的多
我们以N=2009为例计算
N=2009,M=3时最后被杀死的人记为F(2009,3),或者可以简单的记为F(2009)
假设现在还剩下n个人,则下一轮将杀死[n/3]个人,[]表示取整,还剩下n-[n/3]个人
设这n个人为a1,a2,...,a(n-1),an
从a1开始报数,一圈之后,剩下的人为a1,a2,a4,a5,...a(n-n mod 3-1),a(n-n mod 3+1),..,an
于是可得:
1、这一轮中最后一个死的是a(n-n mod 3),下一轮第一个报数的是a(n-n mod 3+1)
2、若3|n,则最后死的人为新一轮的第F(n-[n/3])个人
若n mod 3≠0 且f(n-[n/3])<=n mod 3则最后死的人为新一轮的第n-[n/3]+F(n-[n/3])-(n mod 3)人
若n mod 3≠0 且f(n-[n/3])>n mod 3则最后死的人为新一轮的第F(n-[n/3])-(n mod 3)人
3、新一轮第k个人对应原来的第 3*[(k-1)/2]+(k-1)mod 2+1个人
综合1,2,3可得:
F(1)=1,F(2)=2,F(3)=2,F(4)=1,F(5)=4,F(6)=1,
当f(n-[n/3])<=n mod 3时 k=n-[n/3]+F(n-[n/3])-(n mod 3),F(n)=3*[(k-1)/2]+(k-1)mod 2+1
当f(n-[n/3])>n mod 3时 k=F(n-[n/3])-(n mod 3) ,F(n)=3*[(k-1)/2]+(k-1)mod 2+1
这种算法需要计算 [log(3/2)2009]次 这个数不大于22,可以用笔算了
于是:
第一圈,将杀死669个人,这一圈最后一个被杀死的人是2007,还剩下1340个人,
第二圈,杀死446人,还剩下894人
第三圈,杀死298人,还剩下596人
第四圈,杀死198人,还剩下398人
第五圈,杀死132人,还剩下266人
第六圈,杀死88人,还剩下178人
第七圈,杀死59人,还剩下119人
第八圈,杀死39人,还剩下80人
第九圈,杀死26人,还剩下54人
第十圈,杀死18人,还剩36人
十一圈,杀死12人,还剩24人
十二圈,杀死8人,还剩16人
十三圈,杀死5人,还剩11人
十四圈,杀死3人,还剩8人
十五圈,杀死2人,还剩6人
F(1)=1,F(2)=2,F(3)=2,F(4)=1,F(5)=4,F(6)=1,
然后逆推回去
F(8)=7 F(11)=7 F(16)=8 f(24)=11 f(36)=16 f(54)=23 f(80)=31 f(119)=43 f(178)=62 f(266)=89 f(398)=130
F(596)=191 F(894)=286 F(1340)=425 F(2009
方兴东:互联互通问题的本质与对策
互联网之父温顿·瑟夫认为,从技术角度而言,普遍性的自由连接,是互联网默认的前提,大家不用多讨论,也不要多说规则,这是默认的前提。 弥尔顿·穆勒是全球网络治理领域最有名的专家之一,他认为无缝链接,无边界、透明是整个互联网信仰性的法则,在我们基因里,这种开放是一种物理定理,是国际互连网协会描述的互联网...
影响深远的家庭教育是什么?
夏尔·戴高乐是亨利夫妇的第二个孩子,全名叫夏尔·安德列·瑟夫·马里·戴高乐。他生在里尔,长在巴黎,所以戴高乐后来常自称“巴黎的小里尔人”。一家人在饭前都要念上一遍祝福经,然后边吃饭边用拉丁语就各种问题发表议论。每星期四下午,亨利总要率领着全家去拿破仑墓或去凯旋门前静默致哀;星期天则带...
互联网是谁发明的?
互联网不是一个人发明的,而是一个群体共同努力的结果。互联网之父这一美称被先后授予多人,包括:蒂姆·伯纳斯·李(Tim Berners-Lee)、温顿·瑟夫(Vint Cerf 原名:Vinton Gray "Vint" Cerf )、罗伯特・卡恩(Robert Elliot Kahn)等, 所以“互联网之父”不是一个人,而是一个群体。互联...
TCP\/IP 网的 IP 协议的基本任务是通过互联网传送什么
负责把数据报交付给主机:当目标主机与源主机处于不同的物理网络时,IP负责将数据报路由到相应的目标网络上,完成路由的物理设备称为路由器
IP协议(即互联网协议)的基本任务是什么?
1 首先抛掉原有在大脑的IP地址概念 例如IP地址由网络ID和主机ID构成及与掩码的关系 2仔细理解IP数据报的封装,IP数据报格各部的意义,3了解IP地址解析,将主机或路由器将“下一跳”(主机\/路由器)IP地址翻译成相应的物理网络地址的过程叫IP地址解析(地址解析协议ARP)4IP路由协议 一时说不明白,不过...
因特网是在具体哪一年诞生的?
这个网络在不长的时间里迅速发展成一个全球性网络的根本原因以及进一步发展面临的主要问题。 因特网是当今世界上最大的信息网络。自80年代以来,它的应用已从军事、科研与学术领域进入商业、传 播和娱乐等领域,并于90年代成为发展最快的传播媒介。 因特网是在计算机网络的基础上建立和发展起来的,可以说是一个用...
描述一下TCP\/IP的工作方式?
3、处理路径、流控、拥塞等问题。三传输层 提供应用程序间的通信。其功能包括:1、格式化信息流;2、提供可靠传输。为实现后者,传输层协议规定接收端必须发回确认,并且假如分组丢失,必须重新发送。四应用层 向用户提供一组常用的应用程序,比如电子邮件、文件传输访问、远程登录等。远程登录TELNET使用...
电影《环形使者》的细节问题,还有如何评价这个电影?
【细节上的缺憾:国内片源被压缩了几分钟剧情,导致部分事实观众看不到】其实刚看完电影,我个人也觉得电影《环形使者》的剧情有些叙述上的问题。诸如你提到的,貌似花瓶的女主和老威利斯的爱情描写过于简单潦草。囧瑟夫和农场女主人的激情戏略显突兀。此外反派小时候对母亲的感情表达的跟后面剧情有些抵触...
物理问题
粒子加速器是研究宇宙和物质基本问题的主要设备,美国在加速器的建造方面走在世界最前列.随着超导体技术的发展,在1988年美国国家科学基金会批准了建造至 今为止...随着超导技术的发展,超导量子干涉器件的 应用必然不断地扩大. 许多科学家坚信,未来的超大容量快速计算机一定会用到超导的,也就是使用约 瑟夫逊元件的超高速...
TCP\/IP 协议的具体描述
通俗而言:TCP负责发现传输的问题,一有问题就发出信号,要求重新传输,直到所有数据安全正确地传输到目的地。而IP是给因特网的每一台电脑规定一个地址。1974年12月,卡恩、瑟夫的第一份TCP协议详细说明正式发表。当时美国国防部与三个科学家小组签定了完成TCP\/IP的协议,结果由瑟夫领衔的小组捷足先登,首先制定出了通过...
主父师开同: 在M比较小的时候 ,可以用笔算的方法求解,M=2 即N个人围成一圈,1,2,1,2的报数,报到2就去死,直到只剩下一个人为止.当N=2^k的时候,第一个报数的人就是最后一个死的,对于任意的自然数N 都可以表示为N=2^k+t,其中t<n/2 于是当有...
福海县18059411201: C++ 约瑟夫问题: 求源代码 及 说明约瑟夫问题:M个人围成一圈,从第一个人开始报数,数到N的人出圈;再由下一个人开始报数,数到N的人出圈;…….... - ?
主父师开同:[答案] #include"stdio.h"int main(void){ int a[100] = {0}; int b[100] = {0}; int m = 0,n = 0; int i = 0,/...
福海县18059411201: 什么是约瑟夫环问题? - ?
主父师开同: 是一个数学的应用问题: 已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围.从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列. ...
福海县18059411201: 约瑟夫问题 - ?
主父师开同: #include<stdio.h> #include<stdlib.h>struct node{int data;struct node *next; };int main(){int i,j,k,m,n;struct node *head,*p,*q;scanf("%d%d",&n,&m); p=(struct node*)malloc(sizeof(struct node));p->data=1;p->next=p;head=p; for(i=2;i<=n;...
福海县18059411201: 约瑟夫问题 - 猴子选大王 - ?
主父师开同: 【问题描述-猴子选大王】:M只猴子要选大王,选举办法如下:所有猴子按1-M编号围坐一圈,从1号开始按顺序1,2,,,K报数,凡报到K的猴子退出到圈外,如此循环报数,直到圈内只剩下一只猴子时,这只猴子就是大王.M和K由输入文件提供,...
福海县18059411201: 数据结构试验设计上老师提出了一个问题,关于线性结构的应用,约瑟夫环问题 小弟才疏学浅,约瑟夫问题的一种描述是:编号为1,2,...,n的n个人按顺时针... - ?
主父师开同:[答案] #include struct list//建立一个结构体,包括每个人的编号,密码和下一级的指针 { int id; struct list *next; }; list* input(int n)//链表的初始化 { list *p,*q,*l; int i,m; p=new list; l=new list; l->next=NULL; p=l; p->id=1;//第一个人的初始化 for(i=2;iid=i; q->next=...
福海县18059411201: 数据结构约瑟夫问题 - ?
主父师开同: 当然是m-1,报数是m次 但查询时要向后移动m-1次 当前位置不用执行 例如 当前是一号 m=5 向后移动4个位置就是五号
福海县18059411201: 约瑟夫问题 pascal - ?
主父师开同: ... while do,if then 这些如果子结构只有一句代码是不需要BEGIN END 的;或者写成一排,例:for i:=1 to n do inc(c) dec(c) write(c); 懂否?
福海县18059411201: 约瑟夫问题(PASCAL)?
主父师开同: var monkey:array[1..20] of integer; i,j,k,n,m,x:integer; begin readln(n); m:=2; k:=n; for i:=1 to 20 do monkey[i]:=i; i:=1; repeat x:=0; for j:=1 to (m div 2) do if m mod j:=0 then x:=1; if x=1 then begin i:=i+m-1; for j:=i+1 to n do monkey[j-1]:=monkey[j]; n:=n-1; end; m:=m+1; until k=1; 然后你自己把输出做一下;