【2018沈阳现场赛k】Let the Flames Begin

题意

有n个人围成一圈,编号1到n,从1号开始报数,每报到第k个,此人出列,下一个人再从1开始报数,求第m个出列的人的编号(n,m,k ≤ 1e18, m,k其中一个小于1e6)

分析

我们知道,约瑟夫环的出队是有O(n)的递推算法的:f(n) = (f(n-1)+k-1)%n 约瑟夫环数学推导

但是这里是最后一个出列的人的情况(n个人第n个出列)

考虑一下n个人第m个出列,设状态为f(n,m),我们可以假设在m个人中再插上n-m个人,他们都比前m个人晚出队(具体放在哪里不用关心,只要认为他们一定不会先出队就行了),那么递推式就和刚刚的雷同 f(n,m) = (f(n-1,m-1)+k-1)%n,这个式子可以在o(m)的时间内求出答案,适用于m≤1e6的情况

那么当m在1e18的范围内,该怎么办呢?

观察当前递推式,每次都是+k,当超过n的范围时,进行取模运算,可以发现,当k<<n时,在很多次递推操作中都是不需要取模的,而是只有+k操作,那么我们其实可以吧加法转化成乘法来加速

令add = n-m,考虑当前为f(x+add,x)执行t次后需要取模:

f(x+add+t,x+t) = (f(x+add,x) + (t*k)-1)%(x+add+t)+1

为什么这里的模数在一直变化,还可以这么干呢?因为模数每次只增加1,而f每次增加k,所以当k>1的时候,一定会越来越接近模数,直到超过它,当k=1的时候特判就好

计算t值:

一下简写f(x+add,x)为f

f + (t*k)-1 ≥ x+add+t

解得 t ≥ (x+add-f+1) / (k-1)

从这里也可以看出k=1是需要特判的

解出最小t,更新状态 x = x + t

这个复杂度我也不会算了,但感觉不会太大

代码

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 typedef long long ll;
 4 ll n,m,k;
 5 int main() {
 6     ios::sync_with_stdio(false);
 7     int _,ca=0;cin>>_;
 8     while(_--) {
 9         cin>>n>>m>>k; 
10         ll ans = (k-1)%(n-m+1)+1;
11         if(k==1) ans = m;
12         else if(k >= m) {
13             for(ll i=2;i<=m;i++) 
14                 ans = (ans + k -1)%(i+n-m)+1;
15         }
16         else {
17             ll now = 1;ll a = n-m;
18             while(now < m) {
19                 ll d = (ll)ceil((now + a - ans)*1.0/(k-1));
20                 if(d == 0) d++;
21                 if(now+d >= m) {   d = m-now;}
22                 now +=d;ll mod = (now+a);
23                 ans = (ans + k*d%mod-1+mod)%mod+1;
24             }
25         }
26         cout<<"Case #"<<++ca<<": "<<ans<<endl;
27     }
28 }

 

posted @ 2018-11-16 11:49  Greenty  阅读(1394)  评论(0编辑  收藏  举报