zoukankan      html  css  js  c++  java
  • 约瑟夫问题

    也许更好地阅读体验

    (mathcal{Description})

    经典的约瑟夫问题
    一开始有 (n) 个人围成一个圈,他们的编号从(0)(n-1), 从 (1) 开始顺时针报数, 报出 (m) 的人被机关处决.
    然后下一个人再从 (1) 开始报数, 直到只剩下一个人.
    问最后剩下的人的编号.

    (nleq 10^{18}, mleq 1000)

    (mathcal{Solution})

    那些算法书总是把约瑟夫问题作为链表的例题来讲,初学者接触链表时还不会递推,于是一直以为只是一道链表题...

    这么大的数据,当然不能链表模拟
    考虑递推,还是那句话,递推的东西总是有联系的
    递推时往往假设一个状态是已知的,然后试图推另一个状态,如果有递推式的话,我们就只要将最简单的状态手动求出即可
    假设现在有(n)个人,第一个被处决的人的编号肯定是(k=(m-1)\%n)
    当编号为(k)的人被处决后,接下来我们认为是从原编号为(k+1)的人开始重新编号,然后就是一个(n-1)个人的约瑟夫问题了
    当然,这(n-1)个人的答案肯定不是最终答案,因为他是重新编号后的答案,所以我们要将其恢复原编号
    因为新编号为(0)的人前面有(m-1)个人,也就是编号左移了(m),所以最终的答案应该要加上(m)
    (f[n])为长度为(n)的最后剩下的人的编号
    则有(f[n]=(f[n-1]+m)\%n)

    于是我们得到了一个(O(n))的做法
    但这还不够
    我们继续看这个方程,发现(n)的答案可以被(h)共用
    只需要满足(f[n]+(h-n)m < n)
    也就是每次可以直接更新之后的(k=frac{n-f[n]}{m})个长度的答案
    于是就将复杂度大大降低了

    (mathcal{Code})

    /*******************************
    Author:Morning_Glory
    LANG:C++
    Created Time:2019年09月01日 星期日 15时54分02秒
    *******************************/
    #include <cstdio>
    #include <algorithm>
    #define ll long long
    using namespace std;
    ll n,m,ans,now;
    int main()
    {
    	scanf("%lld%lld",&n,&m);
    	ans=0,now=1;
    	while (now<n){
    		ll inc=min((now-ans)/m,n-now);
    		if (!inc)	inc=1;
    		now=now+inc;
    		ans=(ans+inc*m)%now;
    	}
    	printf("%lld
    ",ans+1);
    	return 0;
    }
    

    如有哪里讲得不是很明白或是有错误,欢迎指正
    如您喜欢的话不妨点个赞收藏一下吧

  • 相关阅读:
    前台js的复制与粘贴
    idea
    前台 js easyUI datagrid 杂记 验证(disable)
    《命运赋》
    前台
    js 、 java去除字符串中的数字
    【 协议 】 freemodbus的分层结构分析
    王爽 汇编11.10(2)编程用串传送指令,将F000H段中最后的16个字节复制到data段中
    王爽 汇编11.10(1)编程用串传送指令,将data段中的第一个字符串赋值到它后面的空间中
    汇编语搜索言中32位CPU多出的两个FS、GS段寄存器,全称是什么啊?
  • 原文地址:https://www.cnblogs.com/Morning-Glory/p/11442807.html
Copyright © 2011-2022 走看看