Josephus

第二次坑在了约瑟夫问题上,话说模拟的约瑟夫我还没过掉,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 }
View Code

 来自迪神

至于时间复杂度,肯定$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})$

原文地址:https://www.cnblogs.com/2018hzoicyf/p/11624950.html