zoukankan      html  css  js  c++  java
  • [学习笔记]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)))
  • 相关阅读:
    oralce 10g(10.2.0.1) 冷备份从64位恢复到32位
    Java反序列化漏洞总结
    正则表达式优化总结
    机器学习笔记(持续更新)
    Java反序列化漏洞分析
    ThinkPHP5 远程命令执行漏洞分析
    Joomla对象注入漏洞分析
    crontab一句话后门分析
    WeCenter3.1.7 blind xxe 分析
    Redis Getshell总结
  • 原文地址:https://www.cnblogs.com/yoshinow2001/p/14458049.html
Copyright © 2011-2022 走看看