有關經典約瑟夫問題的四種解法


    

  約瑟夫問題是信息學奧賽中的一類經典且重要的題型,在平常的測試中屢屢出現。

  

  通常題設可抽象為:一開始有 $n $個人圍成一個圈, 從 $1 $開始順時針報數, 報出 $m $的人被踢出游戲.。然后下一個人再從$ 1 $開始報數,直到只剩下一個人。

     或者:曾經有個人在他身邊,然而現在只剩他一個人。$Who$  $are$  $you$$?$  $Who$ $am$ $I$$?$  $Why$ $am$ $I$ $here$$? $走的越來越慢,人越來越少,可終於還是只剩一個了呢。他們圍成一圈,隨機了一個人作為$1$號,然后逆時針依次編號。$1$號開始報數,報到 $1$,他走了;然后$2$號開始報數,$2$號報了$1$,$3$ 號報了$2$ ,於是$3$ 號也走了……每一輪都從上一次出局的下一個人開始報數,第 $i$輪從$1$ 報到$i$ ,報 $i$的人出局。直到只剩他一個人。卻早已不記得他自己是誰。

 

  針對不同的數據范圍,可以存在如下幾種做法:

1. $O(nm)$

  $O(nm)$的復雜度適用於$n,m$都在$30000$以內的情況,此類題型較少,例如“約瑟夫游戲”一題,$n,m<=30000$,由於隨着游戲的不斷進行,需要枚舉的人數越少,所以復雜度實際低於$O(nm)$。算法思路:暴力模擬即可。

  

#include<bits/stdc++.h>
using namespace std; int T,N,M; bool v[1000100]; void wk(){ memset(v,0,sizeof(v)); scanf("%d%d",&N,&M); int t=0,num=0,pos=1; while(1){ if(v[pos]){ ++pos; if(pos==N+1) pos=1; continue; } ++num; if(num==M){ if(t==N-1){ printf("%d\n",pos); return; } v[pos]=1,++t,num=0; } ++pos; if(pos==N+1) pos=1; } } int main(){ scanf("%d",&T); while(T--) wk(); return 0; }
暴力模擬約瑟夫問題

 

2.$O(n)$

  $O(n)$算法已經適用於大多數約瑟夫問題,讓$n<=1e7$的數據范圍可以被輕松解決,考慮以任意一人為起點,選出第$m$個人后的編號變化,設起始$id==0$,選出第$m$個人后,$id->(id+m)$,再回歸到原來的圓形,設$i$表示第$i$輪游戲,那么整體的公式即為$(id+m)$%$(n-i+1)$。倒序枚舉即可。也可以用$dp$方式實現,或者正序枚舉,將公式改變為$(id+m)$%$(i+1)$,最后答案即為$id+1$。

  

#include<bits/stdc++.h>
#define re register
using namespace std; int T,n,ans,m; inline int read(){ re int a=0,b=1; re char ch=getchar(); while(ch<'0'||ch>'9') b=(ch=='-')?-1:1,ch=getchar(); while(ch>='0'&&ch<='9') a=(a<<3)+(a<<1)+(ch^48),ch=getchar(); return a*b; } signed main(){ T=read(); while(T--){ n=read(),m=read(),ans=0; if(m==1){printf("%d\n",n);continue;} for(re int i=n;i>=1;--i) ans=(ans+m)%(n-i+1); printf("%d\n",ans+1); } return 0; }
O(n)遞推約瑟夫問題

 

3.$O(mlogn)$

  此類算法並不常見,但由於一些毒瘤出題人緣故,針對$n<=1e9,m<=1e5$類型的數據范圍,我們不得不采用特別的遞推方式,通過打表可以發現,保持$m$不變,$n$每加一,答案在模$n$意義下加$m$,注意:此時的$n$是一個變化的$n$,那么可以通過對$n$的遞推處理,將$O(n)$級別的枚舉,轉化為在答案值域區間上的選擇性跳躍,從而將以$n$為基礎的算法轉向以$m$為基礎的算法,可以處理該類毒瘤問題

  

#include<bits/stdc++.h>
#define int long long
#define re register
using namespace std; int t,n,m; inline int read(){ re int a=0,b=1; re char ch=getchar(); while(ch<'0'||ch>'9') b=(ch=='-')?-1:1,ch=getchar(); while(ch>='0'&&ch<='9') a=(a<<3)+(a<<1)+(ch^48),ch=getchar(); return a*b; } signed main(){ t=read(); while(t--){ n=read(),m=read(); re int now=1,ans=1,nxt; while(now<=n){ nxt=(now-ans)/(m-1); if(now+nxt>=n){ ans=ans+(n-now)*m; break; } now=now+nxt+1; ans=(ans+(nxt+1)*m-1)%now+1; } printf("%lld\n",ans); } return 0; }
O(mlogn) 基於值域的約瑟夫問題

 

4.$O(log_m^n)$

  此類算法極其不常見,僅適用於$n$個人圍成一圈,從$1$號開始依次報數,當報到$m$時,報$1$、$2$、…、$m-1$的人出局,下一個人接着從$1$開始報,保證$(n-1)$是$(m-1)$的倍數,最后剩的一個人獲勝的情況。通過打表,可以發現,$f[m^a+m+1]=m$,其余的$f[n]$都滿足$f[n][n-m+1]$,不妨$ n=m^a+(m-1)*k(m^a<n≤m^{a+1})$,則$f[n]=k*m$。時間復雜度$O(log_m^n)$

 

 1 #include<cstdio>
 2 using namespace std;
 3 long long n,m;
 4 signed main(){
 5     long long i;
 6     scanf("%lld%lld",&n,&m);
 7     for(i=1;i<n/m;i*=m);
 8     printf("%lld\n",(n-i)/(m-1)*m);
 9     return 0;
10 }
約瑟夫問題

 

 

 

  至此,通過不同的數據范圍選擇不同的算法,一般的約瑟夫問題已經可以完全解決。

                            $Over$


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM