題意
有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 }
