CCF-CSP 遊戲
問題描述
試題編號:201712-2 試題名稱:遊戲 時間限制:1.0s 記憶體限制:256.0MB 問題描述 有n個小朋友圍成一圈玩遊戲,小朋友從1至n編號,2號小朋友坐在1號小朋友的順時針方向,3號小朋友坐在2號小朋友的順時針方向,……,1號小朋友坐在n號小朋友的順時針方向。 遊戲開始,從1號小朋友開始順時針報數,接下來每個小朋友的報數是上一個小朋友報的數加1。若一個小朋友報的數為k的倍數或其末位數(即數的個位)為k,則該小朋友被淘汰出局,不再參加以後的報數。當遊戲中只剩下一個小朋友時,該小朋友獲勝。 例如,當n=5, k=2時: 1號小朋友報數1; 2號小朋友報數2淘汰; 3號小朋友報數3; 4號小朋友報數4淘汰; 5號小朋友報數5; 1號小朋友報數6淘汰; 3號小朋友報數7; 5號小朋友報數8淘汰; 3號小朋友獲勝。 給定n和k,請問最後獲勝的小朋友編號為多少? 輸入格式 輸入一行,包括兩個整數n和k,意義如題目所述。 輸出格式 輸出一行,包含一個整數,表示獲勝的小朋友編號。 樣例輸入 5 2 樣例輸出 3 樣例輸入 7 3 樣例輸出 4 資料規模和約定 對於所有評測用例,1 ≤ n ≤ 1000,1 ≤ k ≤ 9。
題解1
最容易想到的方法應該是用陣列來標記是否已經出局。首先,初始化編號陣列,然後開始報數,每次都檢查是否已經出局,若已經出局則直接跳過,否則檢查報數是否為k的倍數或其末位數(即數的個位)為k,如果是,則將該陣列元素置為0,即標記為出局。一直報數,直到陣列中非 0 元素只有1個才停止,最後輸出這個非 0 元素的索引即為獲勝小朋友的編號。
n, k = map(int, input().split())# 讀取輸入n, k lst = list(range(1, n + 1))# 編號列表 count = 0# 報數計數 if k != 1: while lst.count(0) != n - 1:# 一直報數,直到只剩下 1 個小朋友 for i in range(n): if lst[i] == 0:# 該小朋友已出局,不再報數 continue else: count += 1# 報數 s = str(count) if count % k == 0 or int(s[len(s) - 1]) == k:# 報數為k的倍數或其末位數(即數的個位)為k lst[i] = 0# 標記為已出局 for i in range(1, n + 1): if lst[i - 1] > 0:# 輸出未出局的小朋友 print(i)# 輸出結果 break else:# 若 k 為1,則直接輸出最後 1 個小朋友的編號 print(n)
題解2
用佇列模擬的方式來解題。首先,列表中儲存 n 個小朋友的編號,每次取出第 1 個元素進行判斷,如果報數不為k的倍數並且其末位數(即數的個位)也不為k,則又將其加入到列表的尾部,直到編號列表中沒有元素都
n, k = map(int, input().split())# 讀取輸入n, k lst = list(range(1, n + 1))# 編號列表 count, res = 1, 1 while lst: res = lst[0]# 取出第1個元素 lst.remove(res)# 出佇列,不能用pop(),因為pop是從列表尾部彈出元素 if not (count % k == 0 or count % 10 == k):# 報數不為k的倍數並且其末位數(即數的個位)也不為k lst.append(res)# 入佇列 count += 1# 報數 print(res)# 輸出結果
知識點補充
列表的增刪操作
- append 方法
>>>lst = [1, 2, 3] >>>lst.append(4)# 在尾部增加元素 >>>lst [1, 2, 3, 4]
- insert 方法
>>>lst.insert(2, 5)# 在指定位置插入元素 >>>lst [1, 2, 5, 3, 4]
- extend 方法
>>>lst.extend([6, 7])# 在列表末尾一次性追加另一個序列中的多個值 >>>lst [1, 2, 5, 3, 4, 6, 7]
- pop 方法
>>>lst.pop()# 移除列表尾部的元素 7 >>>lst [1, 2, 5, 3, 4, 6]
- remove 方法
>>>lst.remove(5)# 移除列表中的指定元素 >>>lst [1, 2, 3, 4, 6]
- clear 方法
>>>lst.clear()# 清空列表中的所有元素 >>>lst []
約瑟夫問題
約瑟夫環(約瑟夫問題)是一個數學的應用問題:已知 n 個人(以編號 1,2,3…n 分別表示)圍坐在一張圓桌周圍。從編號為 k 的人開始報數,數到m的那個人出列;他的下一個人又從1開始報數,數到m的那個人又出列;依此規律重複下去,直到圓桌周圍的人全部出列。通常解決這類問題時我們把編號從 0~n-1,最後 [1]結果+1即為原問題的解。
本題的情景與約瑟夫問題類似,因此也可以利用約瑟夫環的遞推公式來解題。約瑟夫環的標準數學(找規律)解法:
-
將 k 固定,設解
ANS[n,k] = ans(n)
-
具有 m 個數的 m 約瑟夫環中,刪除一個數之後,相當於重新標號後的
m-1
約瑟夫環問題。 -
設從 a(1) 開始的 m 約瑟夫環答案為
ans(m) = s
,在 m+1 約瑟夫環中,由於經歷了重新標號,m 中的標號 a(i) 應該為a((k+i)%(m+1))
,所以答案下標也要從 s 移動到(s+k)%(m+1)
。 -
公式:
ans(1) = 1, ans(m+1) = (ans(m)+k)%(m+1)
只要知道公式,程式碼實現就十分簡單了,如下:
n, k = map(int, input().split())# 讀取輸入的 n 和 k s = 0 for i in range(2, n + 1):# 公式計算 s = (s + k) % i print(s + 1)# 輸出結果
由於題目中除了約瑟夫問題中要求的出局條件外,還要求報數的末位數(即數的個位)為k的也出局,所以上述程式碼只有部分測試樣例能夠通過,提交只能得30分。但是,約瑟夫問題確實值得了解與學習。