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)))
  • 相关阅读:
    PAT顶级 1015 Letter-moving Game (35分)
    PAT顶级 1008 Airline Routes (35分)(有向图的强连通分量)
    PAT顶级 1025 Keep at Most 100 Characters (35分)
    PAT顶级 1027 Larry and Inversions (35分)(树状数组)
    PAT 顶级 1026 String of Colorful Beads (35分)(尺取法)
    PAT顶级 1009 Triple Inversions (35分)(树状数组)
    Codeforces 1283F DIY Garland
    Codeforces Round #438 A. Bark to Unlock
    Codeforces Round #437 E. Buy Low Sell High
    Codeforces Round #437 C. Ordering Pizza
  • 原文地址:https://www.cnblogs.com/yoshinow2001/p/14458049.html
Copyright © 2011-2022 走看看