第二次坑在了约瑟夫问题上,话说模拟的约瑟夫我还没过掉,emmm
题目:一开始有$n$个人围成一个圈, 从$1$开始顺时针报数,
报出$m$的人被机关处决. 然后下一个人再从$1$开始报数, 直到只剩下一个人.
只讲公式递推法喽
首先,钦定序列的下标全部减一,为了取模运算不需特判$0$的情况
把每次操作的序列按时间放在一起是一个倒三角形状,考虑倒推,那么最后一次也就是长度为$1$时,
$x$在最后一次的那个序列里为$0$。考虑把它转移到长度为$2$,即求出在长度为$2$时,$x$在这个序
列里的位置。长度为$1$的序列的第一个点,相当于这个点在$len=2$的序列中,它的前面的点,要么
被删出去了,要么挪到了这个点的后面,然后只要找到在$len=2$时,$len=1$的$1$点的位置,那么,
就有了$x$在$len=2$时的位置
转移$len=i$式子为$pos=(m+pos) mod i$
简单口胡:
先找到$len=i-1$序列中$1$在$len=i$序列中位置,为$(m-1) mod i$
然后在这个位置数$pos-1$个,$pos=((m-1) mod i+pos-1) mod i$
递退到最后,答案为$pos+1$
至于模拟题
$n<=1e9$,需要一个有效的优化,考虑柿子$pos=(pos+m) mod i$中取模在大部分时间可能没用
结合图可以发现,随着$i$在变大,+m越来越不能使$pos$跳到前面,那么可以算一个$k$使得$pos$可以直接跳一大段
则有不等式$k*m+pos<k+i-1$,每次算出即可

1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 using namespace std; 5 inline int minn(int a,int b){return a<b?a:b; } 6 int T,n,m; 7 int main(){ 8 //freopen("da.in","r",stdin); 9 scanf("%d",&T); 10 while(T--){ 11 scanf("%d%d",&n,&m); 12 int pos=0; 13 for(int i=2,k;i<=n;){ 14 //k*m+pos<k+i 15 //pos=(m+pos)%i; 16 //cout<<(i-pos-1)/(m-1)-1<<endl; 17 k=0; 18 if(((i-pos-1)/(m-1)-1)>=0)k=minn(n-i+1,(i-pos-1)/(m-1)-1); 19 i+=k;pos+=k*m; 20 //cout<<i<<" "<<pos<<endl; 21 if(i<=n){ 22 pos=(pos+m)%i; 23 ++i; 24 } 25 } 26 printf("%d ",pos+1); 27 } 28 return 0; 29 }
来自迪神
至于时间复杂度,肯定$O(能过)$,简单研究一下发现,在$len<=m$,那么每次只能减$1$,当$len>m$时,几乎都是每次$*(frac{m-1}{m})$,即为$n*(frac{m-1}{m})^k=m$
为$O(m+log_{frac{m-1}{m}}frac{m}{n})$