版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Feynman1999/article/details/84175475
K Let the Flames Begin
首先,对于经典的约瑟夫环问题,我们记 表示初始有 个人,第 个出队的人是谁(从0号开始报数)。则有递推式 其中 表示每报数 次一个人出队,注意编号从0开始。
递推式的证明: 考虑现在有 个人围成一圈,然后从0开始报数。假设第一个出队的人是 ,这时还有 个人,我们从刚刚出去的那个人的下一个人从0重新编号,那么以当前局面重新开始,第 个出队的人是初始所求的同一个人,但编号不同,差多少呢?即 。 +1 -1 细节手玩一下。
回到本题,由于 可能会很大,但不会同时很大,当 较小的时候( ),直接递推即可。
下面考虑 的情况,会发现模数大部分情况下远大于 ,也就是说可以用乘法代替多次加法,这样可以降低时间复杂度。具体代替多少次呢?考虑 , 假设代替 次,则 进行取模的等价条件是 ,即 即代替次数确定了(整除不整除,快加到 了等细节注意一下即可)。
时间复杂度
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e6+10;
ll m,n,k;
ll f[maxn];
int main()
{
//freopen("in.txt","r",stdin);
int t;
cin>>t;
int T=0;
while(t--)
{
T++;
cin>>n>>m>>k;
cout<<"Case #"<<T<<": ";
if(m<=k){//直接递推
ll tp=(n-m+1);
f[1] = (k-1) % tp; //编号从0开始
for(int i=2;i<=m;++i) f[i] = (f[i-1]+k)%(++tp);
cout<<f[m]+1<<endl;
}
else{
if(k==1) cout<<m<<endl;
else{
ll tp = n-m+1;
ll ans = (k-1) % tp; //编号从0开始
ll now = 1;
while(1){
if((tp-ans)%(k-1)==0){
ll x = (tp-ans)/(k-1) -1 ;
x = min(x,m-now);
ans += x*k;
now += x;
tp += x;
if(now==m) break;
ans =(ans + k) % (tp+1);
now +=1;
tp +=1;
if(now==m) break;
assert(now <=m);
}
else{
ll x = (tp-ans)/(k-1);
x = min(x,m-now);
ans += x*k;
now += x;
tp +=x;
if(now==m) break;
ans =(ans + k) % (tp+1);
now +=1;
tp +=1;
if(now==m) break;
assert(now <=m);
}
}
cout<<ans+1<<endl;
}
}
}
return 0;
}