基本约瑟夫环问题详解
前言:”约瑟夫环问题是十分考验思维的,很显然,我的思维能力十分弱,推了好久,才弄明白了。
思维过程是十分艰难的,从中若有指点迷津,解决时间会缩短很多。
约瑟夫问题:已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围。从编号为1的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。通常解决这类问题时我们把编号从0~n-1,最后结果+1即为原问题的解。通常,我们会要求输出最后一位出列的人的序号。那么这里主要研究的是最后一个出列的人的序号要怎么确定。
一般的方法:大家应该都是知道的,就是暴力,模拟,复杂度高达nm,这样的话复杂度是无法承受的,那么需要从问题在去思维,它问题并没有需要你去描述整个过程,
而是只需要你去求出谁是胜利者。
解决思路:我们可以这样想,
为了简化出列的过程:
首先我们把这n个人的序号编号从0~n-1(理由很简单,由于m是可能大于n的,而当m大于等于n时,那么第一个出列的人编号是m%n,而m%n是可能等于0的,这样编号的话能够简化后续出列的过程),当数到m-1的那个人出列,因此我们编号完成之后,开始分析出列的过程:
第一次出列:
一开始的时候,所有人的编号排成序列的模式即为:
0,1,2,3,4,5...n-2,n-1
那么第一次出列的人的编号则是(m-1)%n1,那么在第一个人出列之后,从他的下一个人又开始从0开始报数,为了方便我们设k1 = m%n1(n1为当前序列的总人数)那么在第一个人出列之后,k1则是下一次新的编号序列的首位元素,那么我们得到的新的编号序列为:
k1,k1+1,k1+2,k1+3...n-2,n-1,0,1,2...k1-3,k1-2 (k1-1第一次已出列)
那么在这个新的序列中,第一个人依旧是从0开始报数,那么在这个新的序列中,每个人报的相应数字为:
0,1,2,3....n-2
那么第二次每个人报的相应数字与第一次时自己相应的编号对应起来的关系则为:
0 --> k1
1 --> k1+1
2 --> k1+2
...
n-2 ---> (k1+n-2)%n1(n1为当前序列的总人数,因为是循环的序列,k1+n-1可能大于总人数)
那么这时我们要解决的问题就是n-1个人的报数问题(即n-1阶约瑟夫环的问题)
可能以上过程你还是觉得不太清晰,那么我们重复以上过程,继续推导剩余的n-1个人的约瑟夫环的问题:
那么在这剩下的n-1个人中,我们也可以为了方便,将这n-1个人编号为:
0,1,2,3,4...n-2
那么此时出列的人的编号则是(m-1) % n2(n2为当前序列的总人数),同样的我们设k2 = m % n2,那么在这个人出列了以后,序列重排,重排后新的编号序列为:
k2,k2+1,k2+2,k2+3...n-2,n-1,0,1,2...k2-3,k2-2 (k2-1第一次已出列)
那么在这个新的序列中,第一个人依旧是从1开始报数,那么在这个新的序列中,每个人报的相应数字为:
1,2,3,4....n-2
那么这样的话是不是又把问题转化成了n-2阶约瑟夫环的问题呢?
后面的过程与前两次的过程一模一样,那么递归处理下去,直到最后只剩下一个人的时候,便可以直接得出结果
如序列:
设7个数,数到3弹出
开始序列 0 1 2 3 4 5 6
数数 0 1 2 3 4 5 6
这时将2弹出重新编号
0 1 3 4 5 6
4 5 0 1 2 3
这时可以将序列转变为
3 4 5 6 0(7) 1(8)
0 1 2 3 4 5
所以第i号点对应的上一层的点为(x+3)%7;(x表示当前数到的数)
所以可以默认为最后一个编号为0,然后递归出解。
id=(id'+k)%i (i>=2)
1 #include<cstdio> 2 int main() 3 { 4 int n,m; 5 while(scanf("%d %d",&n,&m)==2&&n&&m) 6 { 7 int ans = 0; 8 for(int i = 1;i <= n;++i) 9 ans = (ans + m)%i; 10 printf("总人数:%d 每次出列的人喊的号数:%d 最后一个出列的人的序号:%d\n",n,m,ans+1); 11 } 12 }