约瑟夫环
来源:互联网 发布:定金和尾款淘宝客 编辑:程序博客网 时间:2024/05/20 17:08
约瑟夫环问题:一圈共有N(1~N)个人,开始报数(1,2,3,...,M),报到M的人自杀,然后重新开始报数,问最后自杀的人是谁?
如图:内环表示人排列的环,外环表示自杀顺序;上面N=41,M=3。
1. 最普通办法就是模拟整个过程:原文地址:http://blog.csdn.net/kangroger/article/details/39254619
建一个bool数组,true表示此人还活着,false表示已经自杀。可以模拟整个过程
- #include<iostream>
- using namespace std;
- int main()
- {
- int N;//人的总个数
- int M;//间隔多少个人
- cin>>N;
- cin>>M;
- bool *p=new bool[N+1];//[1……N]为true表示此人还活着
- for (int i=1; i <= N; i++)
- *(p+i)=true;
- int count=0;//统计自杀的人数
- for (int i=1, j=0; ;i++)//i用来表示循环,j用来计算是不是第N个人
- {
- if (*(p+i))//此人还活着
- {
- j++;
- if (j == M)
- {
- *(p+i)=false;
- j=0;
- count++;//统计自杀的人
- }
- if (count == N)
- {
- cout<<"最后自杀的人是:"<<i<<endl;
- break;
- }
- }
- if(i == N)
- i=0;
- }
- delete []p;
- return 0;
- }
模拟整个过程,复杂度为O(NM)。
2. 可以用数学方法来求解:http://hi.baidu.com/anywei/item/294351b5f432f144ba0e12f2
把问题换个方式描述一下:N个人(编号0~(N-1)),从0开始报数,报到(M-1)的自杀,剩下的人继续从0开始报数。求最后自杀者的编号。
首先,为什么要从0开始编号?答:因为递推式里有%(取余)运算,这个运算必然是从0开始;至于结束,如果是对n取余(%n),当然是n-1了。
其次,为什么要从0开始报数?答:要不然呢?还是从1开始报数,也可以,但是不是很混乱么?所以可以认为是为了和编号想对应。
先总结一下约瑟夫环的递推公式:
f[1]=0; f[i]=(f[i-1]+m)%i; (i>1)
f[1]=1; f[i]=(f[i-1]+m)%i (i>1); if(f[i]==0) f[i]=i;
那么这2个公式有什么不同?
首先可以肯定的是这2个公式都正确。公式1,得到的是以0~n-1标注的最终序号;公式2得到的就是正常的1~n的序号。
公式1的推导:——————————
给出一个序列,从0~n-1编号。其中,k代表出列的序号的下一个,即k-1出列。
a 0, 1, …, k-1, k, k+1, …, n-1
那么,出列的序号是(m-1)%n,且k=m%n(这个真的是显而易见!)。出列k-1后,序列变为
b 0, 1, …, k-2, k, k+1, …, n-1
然后,我们继续从n-1后延长这个序列,可以得到
c 0, 1, …, k-2, k, k+1, …, n-1, n+0, n+1, …, n+k-2
我们取从k开始直到n+k-2这段序列。其实这段序列可以看作将序列b的0~k-2段移到了b序列的后面。这样,得到一个新的序列
c k, k+1, …, n-1, n, n+1, …, n+k-2
好了,整个序列c都减除一个k,得到
d 0, 1, …, n-2
c序列中的n-1, n, n+1都减除个k是什么?这个不需要关心,反正c序列是连续的,我们知道了头和尾,就能知道d序列是什么样的。
从序列a到序列d,就是一个n序列到n-1序列的变化,约瑟夫环可以通过递推来获得最终结果。
剩下的,就是根据n-1序列递推到n序列。假设在n-1序列中,也就是序列d中,我们知道了最终剩下的一个序号是x,那么如果知道了x转换到序列a中的编号x`(同样都是最后一个自杀的人,n-1个人的x相当于n个人x`——这才是逆推真正的含义),不就是知道了最终的结果了么?
下面我们就开始推导出序列a中x的序号是什么。
d->c,这个变换很容易,就是x+k;
c->b,这个变换是网上大家都一带而过的,也是令我郁闷的一个关键点。从b->c,其实就是0~k-2这段序列转换为n~n+k-2这段序列,那么再翻转回去,简单的就是%n,即(x+k)%n。%n以后,k~n-1这段序列值不会发生变化,而n~n+k-2这段序列则变成了0~k-2;这两段序列合起来,就是序列b。
于是乎,我们就知道了,x`=(x+k)%n。并且,k=m%n,所以x`=(x+m%n)%n=(x+m)%n。公式1就出来了:f[i]=(f[i-1]+m)%i。当然,i=1就是特殊情况了,f[1]=0。这里还有一个小问题。也许你会迷惑为什么x`=(x+m%n)%n=(x+m)%n中的%n变成公式中f[i]=(f[i-1]+m)%i中的%i?其实这个稍微想想就能明了。我们%n就是为了从序列c转换到序列b——这是在n-1序列转换成n序列时%n;那么从n-2转换到n-1呢?不是要%(n-1)了吗?所以这个值是变量,不是常量。
有了递推公式就可以在O(N)时间求出结果:
#include<iostream>using namespace std;int main(){int N;//人的总个数int M;//间隔多少个人cin>>N;cin>>M;int result=0;//N=1情况for (int i=2; i<=N; i++){result=(result+M)%i;}cout<<"最后自杀的人是:"<<result+1<<endl; //result别忘了要加1return 0;}
感性认识:为什么能用a,b,c,d四个序列逆推?答:a,b,c,d是第1个人出列后变成n-1人序列的演变过程,那么从n-1变成n人序列,当然就要加上这第1个被删除的人(就当什么都没发生过)。
- 约瑟夫问题、约瑟夫环
- 约瑟夫环
- 约瑟夫环
- 约瑟夫环
- 约瑟夫环
- 约瑟夫环
- 约瑟夫环
- 约瑟夫环
- 约瑟夫环
- 约瑟夫环
- 约瑟夫环
- 约瑟夫环
- 约瑟夫环
- 约瑟夫环
- 约瑟夫环
- 约瑟夫环
- 约瑟夫环
- 约瑟夫环
- linux下重启mysql php nginx
- 数据结构----迷宫问题
- 8086CPU寄存器全称
- Python:glob模板
- 心得
- 约瑟夫环
- Activity的四种状态
- [数学][第二阶段-简单数学题][HDOJ-2092]整数解
- ./configure --with-package=dir指定依赖的软件包
- 如何一分钟记住23种设计模式
- [应用代码] android 自动接听电话和挂断 (适合目前所有版本)
- javamail发送邮件
- 进程调度算法
- HTML+CSS学习之浮动