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