[線性表] 約瑟夫環
約瑟夫問題 約瑟斯置換 丟手絹問題 約瑟夫環
問題來歷
據說著名猶太曆史學家 Josephus有過以下的故事:在羅馬人佔領喬塔帕特後,39 個猶太人與Josephus及他的朋友躲到一個洞中,39個猶太人決定寧願死也不要被敵人抓到,於是決定了一個自殺方式,41個人排成一個圓圈,由第1個人開始報數,每報數到第3人該人就必須自殺,然後再由下一個重新報數,直到所有人都自殺身亡為止。
https://www.bilibili.com/video/av7885066
原始問題
N個人圍成一圈,從第一個開始報數,第M個將被殺掉,最後剩下一個,其餘人都將被殺掉
例如N=6,M=5,被殺掉的順序是:5,4,6,2,3,1
模擬遊戲過程
模擬遊戲過程,迴圈陣列報數:O(nm)
#include<iostream>
using namespace std;
main()
{
bool a[101]={0};
int n,m,i,f=0,t=0,s=0;
cin>>n>>m;
do
{
++t;//逐個列舉圈中的所有位置
if(t>n)
t=1;//陣列模擬環狀,最後一個與第一個相連
if(!a[t])
s++;//第t個位置上有人則報數
if (s==m)//當前報的數是m
{
s=0;//計數器清零
cout<<t<<' ';//輸出被殺人編號
a[t]=1;//此處人已死,設定為空
f++;//死亡人數+1
}
}while(f!=n);//直到所有人都被殺死為止
}
遞推公式
參考:https://blog.csdn.net/u011500062/article/details/72855826
遞迴:O(n)
-
第一個被刪除的數為 (m-1)%n。
-
假設第二輪的開始數字為k,那麼這n - 1個數構成的約瑟夫環為k, k + 1, k + 2, k +3, …,k - 3, k - 2。做一個簡單的對映。
k -----> 0 k+1 ------> 1 k+2 ------> 2 ... ... k-2 ------> n-2
這是一個n-1個人的問題,如果能從n-1個人問題的解推出 n 個人問題的解,從而得到一個遞推公式,那麼問題就解決了。假如我們已經知道了n-1個人時,最後勝利者的編號為x,利用對映關係逆推,就可以得出n個人時,勝利者的編號為 (x + k) % n。其中k等於m % n。代入(x + k) % n <=> (x + (m % n))%n <=> (x%n + (m%n)%n)%n <=> (x%n+m%n)%n <=> (x+m)%n
-
第二個被刪除的數為(m - 1) % (n - 1)。
-
假設第三輪的開始數字為o,那麼這n - 2個數構成的約瑟夫環為o, o + 1, o + 2,…o - 3, o - 2.。繼續做對映。
o -----> 0 o+1 ------> 1 o+2 ------> 2 ... ... o-2 ------> n-3
這是一個n - 2個人的問題。假設最後的勝利者為y,那麼n -1個人時,勝利者為 (y + o) % (n -1 ),其中o等於m % (n -1 )。代入可得 (y+m) % (n-1)
要得到n - 1個人問題的解,只需得到n - 2個人問題的解,倒推下去。只有一個人時,勝利者就是編號0。下面給出遞推式:
f [1] = 0;
f [ i ] = ( f [i -1] + m) % i; (i>1)
#include <iostream>
using namespace std;
const int m = 3;
int main()
{
int n, f = 0;
cin >> n;
for (int i = 1; i <= n; i++) f = (f + m) % i;
cout << f + 1 << endl;
}
約瑟夫問題10e100版
-
描述
n個人排成一圈。從某個人開始,按順時針方向依次編號。從編號為1的人開始順時針“一二一”報數,報到2的人退出圈子。這樣不斷迴圈下去,圈子裡的人將不斷減少。由於人的個數是有限的,因此最終會剩下一個人。試問最後剩下的人最開始的編號。 -
輸入格式
一個正整數n,表示人的個數。輸入資料保證數字n不超過100位。 -
輸出格式
一個正整數。它表示經過“一二一”報數後最後剩下的人的編號。 -
樣例
樣例輸入1
9
樣例輸出1
3 -
限制
各個測試點1s -
提示
樣例說明
當n=9時,退出圈子的人的編號依次為:
2 4 6 8 1 5 9 7
最後剩下的人編號為3
先暴力打表,找到規律:
#include<cstdio>
#include<ctime>
#include<cstring>
int vis[10001]= {0};
int main() {
freopen("output.txt","w",stdout);
for(int z=1; z<=10000; z++) {
memset(vis,0,sizeof(vis));
int n,count=0;
n=z;
int now=1,next=1,kill=0;
while(count<n-1) {
do
next=(next-1+1)%n+1;
while(vis[next]);
if(kill) {
count++;
vis[now]=1;
kill=0;
} else
kill=1;
now=next;
}
for(int i=1; i<=n; i++)
if(!vis[i])
printf("%d,",i);
}
return 0;
}
找到規律後,再用高精度