約瑟夫問題 線段樹Timus OJ 1521

2021-05-26 18:27:08 字數 2475 閱讀 9487

約瑟夫問題:n個人圍成一圈,從1號開始報數,報到m就退出,剩下的人從下乙個人開始繼續報數。。。問最後剩下的是誰?更難的就是問所有人依次退出的順 序。

例如,5個人,編號1..5,數3退出,以下是模擬

1 2 3 4 5 第一輪3退出,之後從4繼續

1 2 4 5 第二輪1退出,從2繼續

2 4 5 第三輪5退出,從2繼續

2 4 第四輪2退出,從4繼續

4 4退出

最終退出順序:3 1 5 2 4

約瑟夫問題的難點在於,每一輪都不能通過簡單的運算得出下一輪誰淘汰,因為中間有人已經退出了。因此一般只能模擬,效率很低。

現在考慮,如果不在原始編號上計算,而是每一輪都令所有剩下的人從左到右編號,例如上例,在第2輪開始時,場面上還剩下1、2、4、5,則給1新 編號1,2新編號2,4新編號3,5新編號4。

不妨稱這個編號為「剩餘佇列編號」,這個編號的優點在於,如果知道這輪開始報數者的剩餘佇列編號,那麼就可以直接運算得出此輪淘汰者的剩餘佇列編 號。而且淘汰掉他之後,形成新的剩餘佇列編號進行下一輪,此時開始報數的人在這個新剩餘佇列中的編號正好是上輪淘汰者在原剩餘佇列中的編號。(當然,邊界 的話要迴圈一下)

因此只考慮剩餘佇列編號,這個過程是可以o(n)的時間完成的,即:我們可以用o(n)的時間得出每一輪淘汰者在當輪剩餘佇列中的編號位置。下面 用這個方法模擬上例,括號內為原始編號

1(1) 2(2) 3(3) 4(4) 5(5) --> 剩餘佇列編號3淘汰,對應原編號3

1(1) 2(2) 3(4) 4(5) --> 剩餘佇列編號1淘汰,對應原編號1

1(2) 2(4) 3(5) --> 剩餘佇列編號3淘汰,對應原編號5

1(2) 2(4) --> 剩餘佇列編號1淘汰,對應原編號2

1(4) --> 剩餘佇列編號1滔天,對應原編號4

因此,如果每次淘汰時,我們都能快速的通過被淘汰者在當前剩餘佇列中的編號求出原編號,就解決問題了。(顯然不能每輪淘汰後修改編號,這樣是平方 級別了,沒有優化)

乙個人在當前剩餘佇列中編號為i,則說明他是從左到右數第i個人,這啟發我們可以用線段樹來解決問題。用線段樹維護原編號i..j內還有多少人沒 有被淘汰,這樣每次選出被淘汰者後,在當前線段樹中查詢位置就可以了。

具體細節請參見程式,題目在timus oj 1521.

其實也就是建好線段樹,然後查詢第m+1個數的位置,然後把這個位置到根的路徑都-1.

#include using namespace std;

struct segtree

;segtree ltree[5000000];

int n, m, ln;

void init(int nowat, int tl, int tr)

}void del(int nowat, int tw)

}int findcode(int tcode)

else

} return ltree[i].r;

}int main()

printf("\n");

} return 0;

}

類似的還有pku3750

#include using namespace std;

const int n = 70;

struct segtree

;segtree t[n*4];

char s[n][30];

void create(int k,int l,int r)

}void del(int k,int v)

}int findi(int v)

else

} return t[k].l;

}int main()

scanf("%d,%d",&w,&m);

create(1,1,n);

k = w-1;

for(i=1;i<=n;++i)

} return 0;

}

約瑟夫問題

從1~n中每隔e個出人,求出列順序

1 求第i個出列的人

int jsp(int n,int e,int i)

if(i==0)return (e+n-1)%n+1;

if(n==1)return 1;//safe?

return (jsp(n-1,e,i-1)+e -1)%n +1;

2 求指定的數出列序號i

int jsp(int sum,int every,int num)//sum是總人數,every是每隔幾個人

if(sum==1)return 1;

int now=every%sum;

if(now!=num%sum)//直接從0開始

if(num>now)return 1+jsp(sum-1,every,num-now);

return 1+jsp(sum-1,every,num+sum-every);

return 1;

Codevs 1282 約瑟夫問題 線段樹

codevs 1282 約瑟夫問題 首先,建樹,根節點 l 1,r n,sum n sum 指的是在這個區間內 還剩下的人數。主函式乙個 while 迴圈,一直迴圈到根節點 為 0 也就是所有人都出圈了。另外 根節點的sum tree 1 sum 代表還沒出圈的還有多少人。我們可以將 圓圈看為乙個佇...

線段樹解約瑟夫環

題幹 這道題如果用迴圈鍊錶模擬,時間複雜度很不友好,o mn 絕對超時。聽大佬們說可以用線段樹來做,用線段樹來查詢下一次要刪除的數,時間複雜度最終為o nlogn 網上看了這個大佬關於線段樹的詳解 最終ac include using namespace std struct node node t...

ural1521 線段樹解約瑟夫問題

使用sum陣列來記錄當前區段的個數 pushup來更新子節點與父節點的關係,實現很簡單,但是要注意每次節點個數在減少,注意查詢時要求的長度要模一下當前節點的個數 include include include using namespace std const int maxn 100010 int...