• [学习笔记]2018icpc沈阳K题-约瑟夫问题


    这份题解大概是去年10月份打完组队训练写的,之前存在本地没发到博客,大概是昨天吃饭的时候和小伙伴聊到这题,和他口胡了一下大意,他叫我发给他(x),于是就有了这篇博客


    K.Let the Flames Begin

    题意

    约瑟夫问题,(n)个人,报数到(k)出去,问第(m)个出去的人的初始位置,保证(min{m,k}leq 10^6)

    做法

    呜呜呜场内没写出来

    开始处理之前先把编号改一改,改成(0,1,2,...,n-1)比较好搞(

    • (f(n,m)=[f(n-1,m-1)+k] \%n)
    • (f(n,m))表示(n)个人报数第(m)个出去的人的标号
    • 转移成(n-1)个人,对应的第(m-1)个出去的人,从他开始继续报到(k),对应的就是第(m)个出去的人的标号
    • (n)取模
    • 边界条件(f(n,1)=(k-1)\%n)

    基于这一点能想到对(m)小的情形直接(O(m))暴力

    而对于(kleq m)的情形呢?

    • (k)小,(n)大((mleq n)嘛)意味着间隔很小的距离就跳一次,需要取模的情况会比较少!
      即我们考虑从(n-m+1)往回推式子(f(n,m)=[f(n-1,m-1)+k]\%n) ,直观分析,需要跳很多步才会遇到一次需要取模的情况。

    • 而我们只要算出最多能连续跳并且不触发取模的步数(t),然后连着跳(t+1)步再对相应的(n')进行取模,这个过程可以(O(1))地实现

    代码

    #include<bits/stdc++.h>
    using namespace std;
    #define rep(i,a,b) for(register ll i=(a);i<=(b);i++)
    typedef long long ll;
    inline ll solve(ll n,ll m,ll k)
    {
    	if(k==1)return m-1;
    	if(m<=k)
    	{
    		ll res=(k-1)%(n-m+1);
    		rep(i,n-m+2,n)res=(res+k)%i;
    		return res;
    	}
    	ll len=n-m+1;
    	ll res=(k-1)%(n-m+1);
    	m--;
    	
    	while(m>0)
    	{
    		ll t=(len-res)/(k-1);
    		if(m<=t)
    		{
    			res=(res+m*k)%(len+m);
    			m=0;
    		}else
    		{
    			res=(res+t*k)%(len+t);
    			res=(res+k)%(len+t+1);
    			m-=t+1;
    			len+=t+1;
    		}
    	}
    	return res;
    }
    
    int main()
    {
    	int T;scanf("%d",&T);
    	ll n,m,k;
    	rep(t,1,T)
    	{
    		scanf("%lld%lld%lld",&n,&m,&k);
    		printf("Case #%lld: %lld
    ",t,solve(n,m,k)+1);
    	}
    	return 0;
    }
    

    核心代码是

    ll len=n-m+1;
    ll res=(k-1)%(n-m+1);
    m--;
    while(m>0){
        ll t=(len-res)/(k-1);
        if(m<=t){
            res=(res+m*k)%(len+m);
            m=0;
        }else{
            res=(res+t*k)%(len+t);
            res=(res+k)%(len+t+1);
            m-=t+1;
            len+=t+1;
        }
    }
    

    这一段。

    (len)表示当前的(n'),res记录答案,用递推式从小到大倒推回去

    每次算出的最多能跳的步数(t)注意和(m)进行比较

    *复杂度分析

    (这一部分证明思路来自@Mobius Meow大神x)

    • 考虑记(y)为当前的(n'),记(x)为当前进行迭代的次数。
    • 对于(y)我们可以以(y=tk)进行估算,同时注意到(egin{aligned}frac{dy}{dx}=frac{tk-k}{k}=t-1end{aligned}) 这样一个近似的关系
    • 得到方程(egin{aligned}frac{dy}{dx}-frac{y}{k}=-1end{aligned})
    • 这个方程很好解,最后再把(y)换成题目里的n,得到(x=kln(n-k)-Ck)
    • 所以整个算法时间复杂度为优秀的(O(klog(n)))
  • 相关阅读:
    Load a properties file Real's Java Howto
    [nodejs]保证你的程序死了还能复活:forever and forever webui MK2 博客园
    为嵌入式系统开发软件和为高性能计算或大型分布式平台开发软件,二者需要的技能在很大称度上相同,我不是第一个发现这一点的人。
    HTTP Range说明 lenoval的专栏 博客频道 CSDN.NET
    『NodeJS』简单的本地 DNS 代理脚本
    Automation | Coyote Point Systems
    Java 高层网络编程
    SQL 语句大全
    浅谈AVG游戏中的脚本
    How to debug Bluetooth
  • 原文地址:https://www.cnblogs.com/yoshinow2001/p/14458049.html
Copyright © 2020-2023  润新知