這道題所用到的資料結構應該是名次樹,名次樹可以由線段樹實現也可以由樹狀陣列實現。而對於類似這道題這樣的乙個只需要刪除和查詢操作的名次樹而言,可以用樹狀陣列實現,因為用樹狀陣列實現無論效率還是**複雜度都較線段樹而言更優,雖然演算法較線段樹而言更加巧妙。
首先,這個資料結構需要乙個用於查詢第k名是什麼的函式select以及乙個用於刪除第k名的函式del。
先假設我們這兩個函式已經擁有了,理一下主要的解題思路。
讀入n和m的值(這句是廢話)
然後我們第乙個想到的應該是記錄我們這一次所刪除的名次,然後對於剩下的進行二分查詢,但是這樣的複雜度是nlog2n的,雖然對於這樣的弱資料還是能過的,但是並不是最好的演算法。
我們可以如下優化:記錄這次我們刪除的名次之前有幾個人。因為通過記錄刪除的之前有幾個人,我們就可以知道當下應當刪除的名次。
本來應該是在這一次刪除的名次之後再找第m個人,但是這樣有可能之後的人數小於m,那樣就要繞到頭開始。所以我們可以做如下變換:
before=(before+m) % 剩餘總人數。 注:%為c語言中取餘運算子,等價於pascal中的mod。
之後我們呼叫select函式找到我們的指標所指向的名次所儲存的東西並將返回值儲存起來。
輸出這個東西。
然後呼叫del函式刪除這個名次上所儲存的內容。
理清了這樣乙個思路以後,我們開始考慮這兩個函式以及如何用樹狀陣列實現。
樹狀陣列的初始值應該是對於每乙個t[i]=i&-i,這樣的話我們就可以通過倍增法去查詢對應的名次中所儲存的內容。
i&-i是樹狀陣列中求二進位制最低位的一種技術,這裡不過多贅述。
而這樣的話,對於del函式其實可以用樹狀陣列最基本的add函式所替代,因為del(k)要做的事就是add(k,-1);
add函式原型:
void add(int k,int d)。這裡k∈[1,n]。
關於select函式,原型可定義為
int select(int k);
在這裡,k∈[0,n)。為什麼這麼定義呢?其實是為了方便before直接使用和便於查詢。
int
select
(int k)
}return c+1
;因為區間開閉的原因,所以應該返回的是c+1。
}
完整的程式:
#include
const
int max=
30002
;
int t[max],n,m,before,p;
int
select
(int k)
}return c+1
;}
void
add(
int k,
int d)
}
int
main
()for
(int a=n;a>=
1;a--)
return0;
}
Codevs 1282 約瑟夫問題
1282 約瑟夫問題 時間限制 1 s 空間限制 128000 kb 題目描述 description 有編號從1到n的n個小朋友在玩一種出圈的遊戲。開始時n個小朋友圍成一圈,編號為i 1的小朋友站在編號為i小朋友左邊。編號為1的小朋友站在編號為n的小朋友左邊。首先編號為1的小朋友開始報數,接著站在...
Codevs 1282 約瑟夫問題
時間限制 1 s 空間限制 128000 kb 題目等級 大師 master 有編號從1到n的n個小朋友在玩一種出圈的遊戲。開始時n個小朋友圍成一圈,編號為i 1的小朋友站在編號為i小朋友左邊。編號為1的小朋友站在編號為n的小朋友左邊。首先編號為1的小朋友開始報數,接著站在左邊的小朋友順序報數,直到...
Codevs 1282 約瑟夫問題 線段樹
codevs 1282 約瑟夫問題 首先,建樹,根節點 l 1,r n,sum n sum 指的是在這個區間內 還剩下的人數。主函式乙個 while 迴圈,一直迴圈到根節點 為 0 也就是所有人都出圈了。另外 根節點的sum tree 1 sum 代表還沒出圈的還有多少人。我們可以將 圓圈看為乙個佇...