zoukankan      html  css  js  c++  java
  • 【转载】约瑟夫环问题

    参考博客:

    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
    ",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
    ",ans);
    17     }
    18     return 0;
    19 }


  • 相关阅读:
    Longest Substring Without Repeating Characters
    Valid Palindrome
    Longest Common Prefix
    模型压缩之网络剪枝(Network Pruning)篇
    不使用现有检测框架实现一个简单的物体检测网络
    Mac 上传本地项目到GitHub
    Ubuntu 解决Unable to acquire the dpkg frontend lock (/var/lib/dpkg/lock-frontend)
    github clone 速度慢解决方法
    bibitem格式参考文献批量生成
    python matplotlib给图中的点加标签
  • 原文地址:https://www.cnblogs.com/Osea/p/11375838.html
Copyright © 2011-2022 走看看