度小滿2018.9.26筆試 小遊戲
題目描述
這是球盒問題中的,n個球有區別,m個盒子有區別,且盒子可以為空(但球都放進盒子裡)的這種情況,這種情況的方案數為,而在本題中,方案數則為。
根據題意表述以及輸入輸出,可以發現輸出其實輸出是輸的那一方(出題人把輸出弄錯了)。為了方便理解,這裡讓輸出為贏的一方。所以: 樣例輸入: 3 2 2 10 3 1 4 1 4 10 樣例輸出: A B A&B
思路
在每個回合,A和B兩個人都只能增加a,或者增加b,所以每種狀態只有兩個分支(增加底數或增加指數)。可以想象,如果考慮所有分支,那麼就能畫出一個二叉樹。 本文認為,如果只考慮當前狀態的兩個分支,然後再判斷下一步走哪個分支,只能算是貪心演算法。而題目說了雙方都是採取的最優策略
那麼怎麼畫這顆二叉樹呢,考慮增加底數為左孩子,增加指數為右孩子。如果左右孩子都小於n,那麼畫出左右孩子;如果只有左孩子小於n,那麼只畫左孩子;右孩子同理;如果左右孩子都大於等於n,那麼該節點為葉子節點。 以輸入為2 2 10為例,所有的葉子節點的高度%2==1。且這種情況,A贏。 以輸入為3 1 4為例,所有的葉子節點的高度%2==0。且這種情況,B贏。 總結:
- 所有的葉子節點再往下走都會大於等於n,所以它們是一個葉子節點。
- 如果,所有的葉子節點的高度%2==1,那麼肯定是A贏。
- 如果某個葉子節點的高度%2==1,那麼從根節點到該葉子節點的這種流程,是A贏。
- 如果,所有的葉子節點的高度%2==0,那麼肯定是B贏。
- 如果某個葉子節點的高度%2==0,那麼從根節點到該葉子節點的這種流程,是B贏。
但如果二叉樹中,兩種葉子節點都有,這種情況要複雜點: 我們為葉子節點賦值,如果高度%2==1,那麼賦值1。如果高度%2==0,那麼賦值0。即節點為1代表A贏,節點為0代表B贏。葉子節點的值,能夠自底向上地傳遞,傳遞規則如下: 被傳遞肯定只有非葉子節點。分析只有一個孩子的非葉子節點,那麼孩子的值就是該節點的值。 分析有兩個孩子的非葉子節點,這裡分兩種情況。
1.非葉子節點的高度%2==0,那麼該節點的分支是由A決定的,那麼該節點的兩個孩子只要有一個1,那麼該節點就為1。即left or right。因為這個節點的分支是A決定的,所以只要有一個分支為1那麼A自然肯定就會選擇為1的這條分支。
2.非葉子節點的高度%2==1,那麼該節點的分支是由B決定的,那麼該節點的兩個孩子只要有一個0,那麼該節點就為0。即left and right。因為這個節點的分支是B決定的,所以只要有一個分支為0那麼B自然肯定就會選擇為0的這條分支。 如上圖所示,假設畫出來的二叉樹是這樣。那麼初始時,有三個葉子節點。 b)中,由於是隻有一個孩子的非葉子節點,所以直接傳遞值。 c)中,由於該節點是由B決定分支,所以取1 and 0即0。 d)中,由於該節點是由A決定分支,所以取1 or 0即1。 所以,最終是A贏。
還有一種特殊情況是a=1 因為底數為1,那麼二叉樹的右孩子能一直產生(1的任何次冪都為1,如果此時n還大於1),但左孩子到達某個高度後就會沒有左孩子了(因為此時b很大,a一旦從1變成2,就會不小於n)。 這種情況下多了平局的情況,而且注意此時雙方會在輸和平局兩種分支中,選擇平局。 以起始為1,4為例,現假設畫出來的二叉樹為這樣,紅色位元組點之後可能還會有分支,但是在本圖中不畫出來,而是看作這些紅色位元組點已經被傳遞到了值。 思路是從上到下分析紅色位元組點。 1.首先是2,4,因為其父節點是A作決定,所以如果2,4被傳遞到的值為1,那麼A贏,但如果值為0,那麼走右分支,結果待定,還得看下一個紅色位元組點。 2.然後如果到了2,5,因為其父節點是B作決定,所以如果2,5被傳遞到的值為0,那麼B贏,但如果值為1,那麼走右分支,結果待定,還得看下一個紅色位元組點。 3.之後就是這樣,交替分析下一個紅色位元組點。 4.最後如果走到了只有右分支的節點,那麼此時平局。
程式碼
T = eval(input())
def solve(a,b,n,step):
#此函式處理a=1的情況
left = (a+1) ** b
right = a ** (b+1)
if (left >= n) & (right >= n):#遞迴終點,也是二叉樹中的葉子節點
if step%2 == 1:
return 1
elif step%2 == 0:
return 0
#遞迴過程
if left >= n:#只有右孩子可以走
return solve(a,b+1,n,step+1)
elif right >= n:#只有左孩子可以走
return solve(a+1,b,n,step+1)
else:#左右孩子都可以走
leftResult = solve(a+1,b,n,step+1)
rightResult = solve(a,b+1,n,step+1)
if step%2 == 1:#B決定
return leftResult and rightResult
elif step%2 == 0:#A決定
return leftResult or rightResult
def solve_one(b,n):
#此函式處理a>1的情況
step = 1
left = 2 ** b
if left >= n:
return None
else:
while(True):
if 2 ** b >= n:
break
result = solve(2,b,n,step)
if (step%2 == 1) and (result == 1):
return 1
if (step%2 == 0) and (result == 0):
return 0
b += 1
step += 1
return None
for i in range(T):
a,b,n = map(int,input().split())
result = 0
if a != 1:
result = solve(a,b,n,0)
else:
result = solve_one(b,n)
if result is 1:
print('A')
elif result is 0:
print('B')
else:
print('A&B')
輸入: 4 2 2 10 3 1 4 1 4 10 1 4 17
使用短路與、短路或
這種情況中,其實根本不用傳遞所有值,因為根節點是由A節點決定的,既然有一個孩子為1,那麼另一個孩子的值也就無所謂了。所以程式碼可以這樣優化。
def solve(a,b,n,step):
#此函式處理a=1的情況
left = (a+1) ** b
right = a ** (b+1)
if (left >= n) & (right >= n):#遞迴終點,也是二叉樹中的葉子節點
if step%2 == 1:
return 1
elif step%2 == 0:
return 0
#遞迴過程
if left >= n:#只有右孩子可以走
return solve(a,b+1,n,step+1)
elif right >= n:#只有左孩子可以走
return solve(a+1,b,n,step+1)
else:#左右孩子都可以走
if step%2 == 1:#B決定
return solve(a+1,b,n,step+1) and solve(a,b+1,n,step+1)
elif step%2 == 0:#A決定
return solve(a,b+1,n,step+1) or solve(a+1,b,n,step+1)
A決定的節點能短路1,B決定的節點能短路0。所以: 在A決定的節點中,要把左右孩子中,更可能為1的孩子放在前面。 在B決定的節點中,要把左右孩子中,更可能為0的孩子放在前面。 雖然說了如上兩個結論,但左右孩子的值到底更可能為0還是1,這是一件不確定的事情(根據a,b,n的取值來決定,而且分析起來挺複雜)。如上程式碼,我是這樣放的:A決定的就先放右孩子,B決定的就先放左孩子。