1. 程式人生 > >[線性表] 約瑟夫環

[線性表] 約瑟夫環

約瑟夫問題 約瑟斯置換 丟手絹問題 約瑟夫環

問題來歷

據說著名猶太曆史學家 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)

  1. 第一個被刪除的數為 (m-1)%n。

  2. 假設第二輪的開始數字為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

  1. 第二個被刪除的數為(m - 1) % (n - 1)。

  2. 假設第三輪的開始數字為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版

  1. 描述
    n個人排成一圈。從某個人開始,按順時針方向依次編號。從編號為1的人開始順時針“一二一”報數,報到2的人退出圈子。這樣不斷迴圈下去,圈子裡的人將不斷減少。由於人的個數是有限的,因此最終會剩下一個人。試問最後剩下的人最開始的編號。

  2. 輸入格式
    一個正整數n,表示人的個數。輸入資料保證數字n不超過100位。

  3. 輸出格式
    一個正整數。它表示經過“一二一”報數後最後剩下的人的編號。

  4. 樣例
    樣例輸入1
    9
    樣例輸出1
    3

  5. 限制
    各個測試點1s

  6. 提示
    樣例說明
    當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;
}

找到規律後,再用高精度