【转载】约瑟夫环问题 约瑟夫环

参考博客:

1、约瑟夫环问题详解

2、约瑟夫环


约瑟夫环问题

什么是约瑟夫环问题

  约瑟夫是犹太军队的一个将军,在反抗罗马的起义中,他所率领的军队被击溃,只剩下残余的部队40余人,他们都是宁死不屈的人,所以不愿投降做叛徒。一群人表决说要死,所以用一种策略来先后杀死所有人。于是约瑟夫建议:每次由其他两人一起杀死一个人,而被杀的人的先后顺序是由抽签决定的,约瑟夫有预谋地抽到了最后一签,在杀了除了他和剩余那个人之外的最后一人,他劝服了另外一个没死的人投降了罗马
  我们这个规则是这么定的:
  在一间房间总共有n个人(下标0~n-1),只能有最后一个人活命
按照如下规则去杀人:

  1. 所有人围成一圈
  2. 顺时针报数,每次报到q的人将被杀掉
  3. 被杀掉的人将从房间内被移走
  4. 然后从被杀掉的下一个人重新报数,继续报q,再清除,直到剩余一人


推导过程:

特例1:q=2,n=2^k

#n = 2
0 1 ==> 0

#n = 4
0 1 2 4 ==> 0 2 ==> 0

#n = 8
0 1 2 3 4 5 6 7 ==> 0 2 4 6 ==> 0 4 ==> 0

定义 Jq=(n=) 为n个人,每次杀死第q个人构成的约瑟夫环最后结果,则有jq=2(n=2^k) = 0


q=2,n=任意数

  当n可以为任意数字的时候,就不会有上面这么简单的站位了,你的走位需要飘逸一点才能活到最后
  举个栗子:n=9
  注:示例途中的下表从1开始,我们也可以看成是从0开始,就不去改图了
示例图1
  能看出来,我们干掉途中的第一个人也就是2,之后就只剩下8个人了,这时候n=8=2^3,这样一来又回到Jq=2(2^k)上了,

这时候我们需要的是找到当前的1号元素
示例图2
  这时候,我们从3号开始,就成了另外一个规模小1的约瑟夫问题(恰好为2^k的特例)。
  此时,我们可以把3号看成新的约瑟夫问题中的1号位置:
  Jq=2(n=8) = Jq=2(n=2^3) = 1,也就是说这里的1代表的就是上一个问题中的3号

  So:Jq=2(n=9) = 3
  答案为3号
  总结下规律:
    

  在q=2的前提下,给出n,我们首先找出,离n最近的一个2^k数,如n=9,那么这个2的幂次方数就是8,同理可得。找到之后,我们需要转换成对应的这个2^k数的约瑟夫环问题,因为其第一个元素即是我们想要的答案

   Jq=2(n) = Jq=2(2^k + t) = 2t+1


 

q=任意数,n=任意数

  说完了特例,应该对约瑟夫环的问题了解了,现在说说q≠2的情况下,应该是什么样的规律
  我们假定:

  
- n — n人构成的约瑟夫环
- q — 每次移除第q个人  


- Jq(n)表示n人构成的约瑟夫环,每次移除第q个人的解 
- n个人的编号从0开始至n-1



0  1  2  ................................   n-1       总共n人
| | | |
q q+1 q+2 ...... n-2 n-1 0 1 2 ...... q-2 (这里是除去q-1这位兄台的剩余n-1人)

设第q个人也就是下标为q-1的那位,杀死,剩下n-1个人,如上


这时,又来重复我们的老套路:将新的被杀的后一个人作为新的0号,于是新的如下:
0 1 2 ...... .......... ........ n-2




示例图3

 

  现在大概知道我们的新的约瑟夫环的下标都是这样来的:在旧的下标基础上,减去一个q,再用计算出的结果对长度取余
  new = (old-q) % n

  反推一下:
  old = (new+q) % n


 1 #include<iostream>
 2 #include<stdio.h>
 3 using namespace std;
 4 
 5 int yuesefu(int n,int m){
 6         if(n == 1){
 7                 return 0; //这里返回下标,从0开始,只有一个元素就是剩余的元素0
 8         }
 9         else{
10                 return (yuesefu(n-1,m) + m) % n; //我们传入的n是总共多少个数
11         }
12 }
13 int main(void){
14         int a,b;
15         cin>>a>>b;
16         cout<<yuesefu(a,b)<<endl;
17 
18         //或者,直接循环迭代,求出来的result如上
19         int result = 0;
20         for(int i = 2;i <= a;i++){
21                 result = (result+b) %i;
22         }
23         cout<<"result = "<<result<<endl;
24         return 0;
25 }
View Code

【题目链接】https://vjudge.net/problem/51Nod-1073

首先,将n个人编号为: 0,1,2,3........k-1,k,k+1........n-1,

第一轮结束之后第k个人,也就是k-1将出列,剩下的人: 0,1,2,3.........k-2, k, k+1.......n-1

下一轮从k开始我们进行重新编号,将上面的编号为:   n-k,n-k+1,n-k+2,n-k+3.............., 0,.1,.........n-k-1,

我们如果知道了这一轮存活下来的人对应的编号是y,那么他本来的编号就是 x=(y+k)%n

由此引出递推式 f(n)=(f(n-1)+k)%n | f(1)=0

【题目链接】https://vjudge.net/problem/UVA-1394

给出n,k,m表示,第一次先出队第m个人,然后从后面的人开始数到k的出列接着从1开始数,

和原来的有了一点变化就是不是从第一个人开始数了,而是先出队第m然后从m+1开始,不过只有第一轮的和后面的公式不一样我们只要算出f(n-1)再推f(n)就好了。

我们将第m人出列后从新对余下的(n-1)人编号,f(n-1)=(f(n-2)+k)%(n-1);  设x=f(n-1),则最后的答案  ans=(x+m)%n+1;

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int main()
 4 {
 5     int n,m,k;
 6     while(~scanf("%d%d%d",&n,&k,&m),(n||m||k)){
 7         int f = 0 ;
 8         for(int i=2;i<n ;i++)
 9             f = ( f+k ) % i ;
10         f = (f+m+n) % n + 1 ;
11         printf("%d\n",f);
12     }
13     return 0;
14 }

【题目链接】http://acm.hdu.edu.cn/showproblem.php?pid=2211

【参考博客】https://blog.csdn.net/u012717411/article/details/43925433

这个就是每次出队是k倍数的人,找到队尾之后将再次从队首开始数1,与约瑟夫有些不同,但不变的是公式推倒的过程。

想要知道f(n),我们不妨先假设得到了x=f(n-1),这里的n表示是第几轮从头开始,我们如果将这个x转为原本的编号呢,如果我们知道他前面死了几个人的话,

直接让x加上这个人数就好了,因为数到k就死一个,也就是说x前面有几个(k-1)就表示上一轮x前面淘汰的人,加上就好,最后边界f(k,k)=k

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int dfs(int n,int k){
 4     if( n == k ){
 5         return k ;
 6     }
 7     int x = dfs(n-n/k,k);
 8     return x + (x-1)/(k-1);
 9 }
10 int main(){
11     int T,n,k;
12     scanf("%d",&T);
13     while(T--){
14         scanf("%d%d",&n,&k);
15         int ans = dfs(n,k) ;
16         printf("%d\n",ans);
17     }
18     return 0;
19 }


猜你喜欢

转载自www.cnblogs.com/Osea/p/11375838.html