题目描述:
题目分析:
(Steins Gate,给出题人点赞!好评!)这么多次模拟赛第一次AK
我们把0~n-1这n个人排成一排:
0 1 2 3 … n-1
然后编号为m%n-1的人会被机关处决,从编号为m%n的人开始重新计数,假设m%n=4:
0
1
2
3
4
5
6
…
n
−
1
n
−
4
n
−
3
n
−
2
0
1
2
…
n
−
5
\begin{aligned} 0~~~~~~~~~~1~~~~~~~~~~2~~~~~~~~3~~~4~~~5~~~6\dots~~~n-1\\ n-4~~~n-3~~~n-2~~~~~~~~~0~~~1~~~2\dots~~~n-5 \end{aligned}
0 1 2 3 4 5 6… n−1n−4 n−3 n−2 0 1 2… n−5
可以看出这一轮相对于下一轮编号增加了4(即m%n),所以下一轮编号为k的人在这一轮编号为(k+m%n)%n
设 f [ n ] f[n] f[n]表示n个人中幸存者的编号,那么有 f [ n ] = ( f [ n − 1 ] + m % n ) % n f[n]=(f[n-1]+m\%n)\%n f[n]=(f[n−1]+m%n)%n
当n<=106时可以O(n)递推,但是n<=109就行不通了。
注意到题目中m<<n,而当m<n时m%n=m,所以可以尝试先把前m项递推求出来,然后式子就变为了: f [ n ] = ( f [ n − 1 ] + m ) % n ( m ≤ n ) f[n]=(f[n-1]+m)\%n~~~(m\le n) f[n]=(f[n−1]+m)%n (m≤n)
而m<<n,可以看出我们可以尝试一次递推多轮而不必管模。
假设我们当前推到了
f
[
j
]
f[j]
f[j],需要找到使得
f
[
j
]
+
(
i
−
j
)
m
>
i
f[j]+(i-j)m>i
f[j]+(i−j)m>i成立的最小的
i
i
i,简单化简可以得到
i
=
⌊
j
m
−
f
[
j
]
m
−
1
⌋
+
1
i=\lfloor\frac {jm-f[j]}{m-1}\rfloor+1
i=⌊m−1jm−f[j]⌋+1,于是可以直接令
f
[
i
]
=
(
f
[
j
]
+
(
i
−
j
)
m
)
%
i
f[i]=(f[j]+(i-j)m)\%i
f[i]=(f[j]+(i−j)m)%i。
经过测试发现这样的递推方式跑的很快(于是我就不管复杂度什么的了,和O(n)递推拍一拍之后直接下一题 )
考完题解是这样的:
真是有yali的风范(我TM上不去Wikipedia啊啊啊啊啊啊)
然后我找到一篇文章:约瑟夫问题(Josephus problem)的klog(n)解法
文章的末尾有复杂度分析(然而我并没有看懂第一步是怎么来的,于是就咕咕咕了 )
Code(代码是按照从1开始编号的方式写的,不如从0开始的简洁,读者可以自行尝试):
#include<bits/stdc++.h>
using namespace std;
int T,n,m;
int main()
{
freopen("mayuri.in","r",stdin);
freopen("mayuri.out","w",stdout);
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
if(m==1) {printf("%d\n",n);continue;}
int f=1;
for(int i=2;i<=min(n,m);i++) f=(f+(m-1)%i)%i+1;
for(int j=m,i;j<n;j=i){
i=min(1ll*n,(1ll*j*m-f)/(m-1)+1);
f=(f+1ll*(i-j)*m-1)%i+1;
}
printf("%d\n",f);
}
}