【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 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM