NOIP算法约瑟夫问题C.docx
- 文档编号:8732976
- 上传时间:2023-02-01
- 格式:DOCX
- 页数:7
- 大小:31.30KB
NOIP算法约瑟夫问题C.docx
《NOIP算法约瑟夫问题C.docx》由会员分享,可在线阅读,更多相关《NOIP算法约瑟夫问题C.docx(7页珍藏版)》请在冰豆网上搜索。
NOIP算法约瑟夫问题C
约瑟夫问题
也称为约瑟夫斯置换,在计算机编程的算法中,类似问题又称为约瑟夫环,又称“丢手绢问题”。
一、一般形式
1.N个人围成一圈,从第一个开始报数,第M个将被杀掉,最后剩下一个,其余人都将被杀掉。
例如N=6,M=5,被杀掉的顺序是:
5,4,6,2,3,1。
2.有n只猴子,按顺时针方向围成一圈选大王(编号从1到n),从第1号开始报数,一直数到m,数到m的猴子退出圈外,剩下的猴子再接着从1开始报数。
就这样,直到圈内只剩下一只猴子时,这个猴子就是猴王,编程求输入n,m后,输出最后猴王的编号。
算法思路:
模拟
(1)由于对于每个人只有在与不在环中两种状态
(2)开始时每个人都在环中
(3)模拟过程,直到环中只剩下一人。
方法一:
设立标记,每个元素标记为出队或在队中
#include
#include
#include
#include
usingnamespacestd;
constintmaxn=310;
inta[maxn];
intmain(){
intp,num,cnt,n,m,tmp;
while(scanf("%d%d",&n,&m)!
=EOF&&(n!
=0||m!
=0)){
memset(a,0,sizeof(a));
p=1;
for(num=1;num<=n;num++){//一共要数n轮
for(cnt=1;cnt<=m;cnt++){//每一轮数到m
while(a[p]==1)p=p%n+1;//如果指针所指已有标记指针后移
tmp=p;//此时p指向环中的第cnt只猴子
p=p%n+1;//指针后移
}
a[tmp]=1;//将第m只猴子标记
}
printf("%d\n",tmp);//第n轮的第m只猴子就是解
}
return0;
}
方法二:
使用数组模拟链表
codevs1282约瑟夫问题
#include
#include
#include
#include
#include
usingnamespacestd;
constintmaxn=30050;
inta[maxn];
intmain(){
intn,m,i,j,p;
scanf("%d%d",&n,&m);
for(i=1;i for(i=1;i<=n;i++){ for(j=1;j printf("%d",a[p]); a[p]=a[a[p]]; } return0; } 二、深入探讨 对于约瑟夫问题,若需要依次记录退出编号的情况: 优化方法是使用线段树维护区间和,并进行单点更新。 具体方法见线段树章节。 对于约瑟夫问题,若只需要求胜利者编号,而不需依次记录退出编号的情况: 优化1: 把问题稍微改变一下,并不影响原意: 问题描述: n个人(编号0~(n-1)),从0开始报数,报到(m-1)的退出,剩下的人继续从0开始报数。 求胜利者的编号。 则第一轮报数出队者的编号: (m%n-1+n)%n; 第二轮报数由剩下的n-1个人组成了一个新的约瑟夫环,从k=m%n开始报数;其编号由第一轮的 k,k+1...n,0,1,...k-2,变化为 0,1,...... ...n-1 若第二轮出队编号为x,则其在第一轮中的编号应该为x’=(x+k)%n=(x+m)%n 推广到i-1个人报数若胜利者编号为x,则其在i个人的约瑟夫环中编号应该为x’=(x+m)%i; 故,令opt[i]表示i个人的约瑟夫环中报m个数的胜利者的编号,则有: opt[1]=0; opt[i]=(opt[i-1]+m)%i(2<=i<=n) opt[n]即为问题的解! 考虑编号问题,若n个人从1开始编号,f[n]+1即为解。 时间复杂度O(n);空间复杂度O(n); #include #include #include #include usingnamespacestd; constintmaxn=310; intopt[maxn]; intmain(){ intn,m,i; while(scanf("%d%d",&n,&m)! =EOF&&(n! =0||m! =0)){ opt[1]=0; for(i=2;i<=n;i++)opt[i]=(opt[i-1]+m)%i; printf("%d\n",opt[n]+1); } return0; } 优化2: 使用迭代算法使空间复杂度降为o (1); #include #include #include #include usingnamespacestd; intf; intmain(){ intn,m,i; while(scanf("%d%d",&n,&m)! =EOF&&(n! =0||m! =0)){ f=0; for(i=2;i<=n;i++)f=(f+m)%i; printf("%d\n",f+1); } return0; } 优化3: 观察上述算法中的变量f,他的初始值为第一个出圈人的编号,但在循环的过程中,我们会发现它常常处在一种等差递增的状态,从f=(f+m)%i,可以看出,当i比较大而f+m比较小的时候,f就处于一种等差递增的状态,这个等差递增的过程并不是必须的,可以跳过。 递推式: f(i)=(f(i-1)+m)%i; 1)对于m=1的情况可以单独讨论: 当f==0时,最终结果就是n-1; 2)如果f+m>i-1则必须取模,使用原方法求结果; 3)如果f+m<=i-1则表示可以进行优化: 根据递推式: 第i轮的结果是由第i-1轮推得的;现要使得等式跳过x轮,在不取模的情况下仍成立,则有: f(i-1)+m+(x-1)*m<=i-1+x-1;//不等式左边为多跳过x-1轮,右边为增加x-1个人出圈 即: 第i-1轮的约瑟夫号码+x*m<=i-1+x轮的总人数-1(号码从0开始) f+m*x <=i-2+x x最大能取到(i-2-f)/(m-1); f(i)=f(i-1)+m,为1轮,则x轮需再增加x-1轮, 得到f(i-1+x)=f(i-1)+x*m,这样就跳过了中间共x重的循环,从而节省了等差递增的时间开销。 可是其中求出来的x-1+i可能会超过n,则: 当x-1+i>n时 x=n-i+1 即x最大可取n-i+1 f=f+m*(n-i+1); 该算法的算法复杂度在m #include #include #include #include usingnamespacestd; intf; intmain(){ intn,m,i,x; while(scanf("%d%d",&n,&m)! =EOF&&(n! =0||m! =0)){ if(m==1)f=n-1; else{ i=2;f=0; while(i<=n){ if(f+m<=i-1){//如果可以优化 x=(i-2-f)/(m-1);//计算x的值 if(i+x-1>=n)x=n-i+1;//如果跳过x轮后超出n,更新x f=f+x*m;//得到当前f i=i-1+x;//得到当前i i++;//计算下一轮 } else{ f=(f+m)%i; i++; } } } printf("%d\n",f+1); } return0; } 笔算,n=300,m=3的约瑟夫问题(编号从1开始),最后胜出者的编号是? 根据优化3的推导,依次求解: 基本式: f(i)=(f(i-1)+m)%i 若f(i-1)+m>i-1只可求f(i) 若f(i-1)+m<=i-1则可直接求f(i+x-1),即f(i+x-1)=f(i-1)+x*m 并且x=(i-2-f)/(m-1) 手算时,由于f(i+x-1)=f(i-1)+x*m即f(i-1)+x*m 故可将f(i-1)+m*x<=i-2+x改为将f+m*x <=i-1+x,即x=(i-1-f)/(m-1)手工取模即可 f (1)=0;f (2)=(0+3)%2=1;f(3)=(1+3)%3=1;f(4)=(1+3)%4=0;f(5)=(0+3)%5=3;f(6)=(3+3)%6=0; 此时i=7,x=(6-0)/2=3 f(9)=0+3*3=9%9=0; 此时i=10,x=(9-0)/2=4 f(13)=0+3*4=12 f(14)=1 此时i=15,x=(14-1)/2=6 f(20)=1+3*6=19 f(21)=(19+3)%21=1 x=(21-1)/2=10 f(31)=1+3*10=31%31=0 x=(31-0)/2=15 f(46)=3*15=45 f(47)=(45+3)%47=1 x=(47-1)/2=23 f(70)=1+3*23=70%70=0 x=(70-0)/2=35 f(105)=0+3*35=105%105=0 x=(105-0)/2=52 f(157)=0+52*3=156 f(158)=159%158=1 x=(158-1)/2=78 f(236)=1+3*78=235 f(237)=1
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- NOIP 算法 约瑟夫 问题
![提示](https://static.bdocx.com/images/bang_tan.gif)