• 【算法学习笔记】54.约瑟夫问题 模拟、逆推动规 SJTU OJ 1038 二哥的约瑟夫


    Description

    话说二哥当年学习数据结构的时候遇到了那道猴子报数的题目,其实这就是经典的约瑟夫问题。

    可是当年的二哥还是个毛头小子,只会用模拟的方法,而其他同学却使用了一些令二哥完全摸不到头脑的方法。

    ……二哥一怒之下改了题目……

    话说当年花果山的猴子要选大王,选举办法如下:

    所有猴子按1-M编号围坐一圈,二哥站在圈中心,由二哥指定一个整数Kn,

    之后猴子们从1号开始按顺序报数,报到Kn的猴子退出到圈外,二哥再报出一个整数Kn+1,

    然后由刚刚退出的猴子的下一只猴子再开始报数,如此循环报数,直到圈内只剩下一只猴子时,这只猴子就是大王。

    由于二哥希望通过此种方法控制花果山,所以现在二哥把他制定的整数序列告诉你,希望你帮他预先算出那只猴子会成为大王。

    Input Format

    第一行 一个整数M,表示一共有M只猴子

    第二行到第M行,每行一个整数 表示二哥即将指定的M-1个整数。这些数都大于0。

    Output Format

    一个整数,表示最后剩下那只猴子的编号。

    Hint

    对于40%的数据,M<=1000, K<=1000

    对于70%的数据,M<=10000, K<=10000

    对于100%的数据,M<=10000, K<=100000000

    Sample Input

    5
    1
    2
    3
    4
    

    Sample Output

    4



     1.链表模拟法 很简单 也能AC 优化就是对K进行取余操作
    但是要注意一点就是 因为我习惯了用1 2 3 来做代号 在取余时可能会出现K恰好是cnt的倍数 所以要进行单独处理 这点可以用换代号为 0 1 2...来解决
    模拟法代码:
    #include <iostream>
    using namespace std;
    
    int front[10000+10];
    int nex[10000+10];
    //模拟法
    void out(int x){
        nex[front[x]] = nex[x];
        front[nex[x]] = front[x];
    }
    
    int main(int argc, char const *argv[])
    {
        int M;
        cin>>M;
        for (int i = 1; i <= M; ++i)
        {
            front[i] = (i==1) ? M : i-1;
            nex[i] = (i==M) ? 1 : i+1;
        }
        int cur = 1;
        int cnt = M;
        int K;
        while(1){
            cin>>K;
            K %= (cnt);
            if(K==0)
                K+=cnt;
            for (int i = 0; i < K-1; ++i)
            {
                cur = nex[cur];
            }
            out(cur);
            cur = nex[cur];
            cnt--;//出去一只猴子
            if(cnt == 1){
                cout<<cur<<endl;
                break;
            }
        }
    
        return 0;
    }
    链表模拟法

      2.数学策略 逆推法 O(n) 

      我们可以发现每一次踢出一个人以后,都是一个新的约瑟夫环问题,而相邻的两个约瑟夫环问题之间有一个关系。

    假设Y1---->Y2 踢出的人在Y1中的编号为s1 = K1-1 (因为从0号人开始报数), 在Y1中编号为s1+1的人在Y2里编号为0 依次类推

      假如我们已经知道了Y2中被踢出的人是s2

        则 s2与s1 可以建立关系

        s2在Y1中的编号 id = s2  +  s1 + 1  =  (s2 + K1) % num[1] 

    也就是说 在第二局里被踢出的人的在第一局里的编号是 在第二局里的编号+上一次的K 然后对第一局人数取余

    我们知道最后一局出局的人的编号是0(只有一个人)

    所以可以逆推出这个人在第一局的编号 即是答案

    #include <iostream>
    using namespace std;
    int K[10000]={0};
    int main(int argc, char const *argv[])
    {
        int M;
        cin>>M;
        int ans = 0;//最后一个人 是 最后一个约瑟夫环的唯一一个人
    
        for (int i = 1; i <= M-1; ++i){
            cin>>K[i];
        }
    
        //从倒数第二个约瑟夫环开始反推 一共有M个约瑟夫环
        for (int i = 2; i <= M; ++i)
        {
            ans = (ans+K[M-i+1]) % i;//此时的上一局有i个人 数了K[M-i+1]才到ans的
            //用0标识初始位置可以有效避免取余后为0的问题
        }    
        cout<<ans+1<<endl;
        return 0;
    }//12 2 4 3 5 76 92 92 9 4 13 13
    逆推动态规划
  • 相关阅读:
    仓位管理 – 1.理论篇
    作为首席架构师,我是如何选择并落地架构方案的?
    asp.net MVC 应用程序的生命周期
    微服务架构优缺点
    一位同事对 Rafy 框架的一些建议及我的回复
    .NET 版本区别,以及与 Windows 的关系
    MIS性能优化常见问题与方案(辅助项目组性能优化的总结贴)
    何时使用静态 API
    2011奥斯卡最佳纪录片《监守自盗(Inside Job)》小结
    Rafy 框架
  • 原文地址:https://www.cnblogs.com/yuchenlin/p/sjtu_oj_1038.html
Copyright © 2020-2023  润新知