蟻本2-2は貪欲法です.
背景:蟻本をやります - 競プロはじめました
テキスト:
硬貨の問題
【入力】3 2 1 3 0 2
620
【出力】
6 (1円玉0枚, 5円玉2枚, 10円玉1枚, 50円玉2枚, 100円玉0枚, 500円玉1枚)
【コード】
c = list(map(int, input().split())) a = int(input()) v = [1, 5, 10, 50, 100, 500] ans = 0 ans2 = [] # 追加 for p in v[::-1]: t = min(a//p, c[v.index(p)]) a -= t*p ans += t ans2.append(t) # 追加 # 追加 ans3 = [str(p) + '円玉' + str(q) + '枚' for p, q in zip(v, ans2[::-1])] print('{} ({})'.format(ans, ', '.join(ans3)))
区間スケジューリング問題
【入力(わざと順番を入れ替えています)】5
4 6 8 1 2
7 9 10 3 5
【出力】
3 (仕事1, 3, 5を選択)
【コード】
n = int(input()) s = list(map(int, input().split())) t = list(map(int, input().split())) inv = sorted([[a, b] for a, b in zip(t, s)]) ans, fin = 0, 0 ans_list = [] # 追加 for a, b in inv: if fin < b: ans += 1 ans_list.append(str(sorted(s).index(b)+1)) # 追加 fin = a print('{} (仕事{}を選択)'.format(ans, ', '.join(ans_list)))
【ポイント】
「私」がある仕事を終えた後,終了時刻が最も早い仕事に飛びくことで,こなせる仕事が他の人より少なくなり得るか?を考えると,これはありえません.
なぜなら,他の仕事を選んだ人がその仕事を終えたときには,「私」はさらに次の仕事を選べており,その仕事は(まだ始まってないかもしれないが)他の人が選ぶどの仕事よりも早く(あるいは同時に)終了するからです.つまり,「私」は選んだ各仕事の終了時刻において,他の人以上の仕事をこなしています.
Best Cow Line
【入力】6
ACDBCB
【出力】
ABCBCD
【コード】
n = int(input()) s = input() ans = '' while len(s) > 0: if s < s[::-1]: ans += s[0] s = s[1:] else: ans += s[::-1][0] s = s[:len(s)-1] print(ans)
Saruman's Army
【入力(最終行:わざと順番を入れ替えています)】6
10
20 30 50 1 7 15
【出力】
3
【コード】
n = int(input()) r = int(input()) x = sorted(list(map(int, input().split()))) i, ans = 0, 0 while i < n: s = x[i] while i < n and x[i] <= s+r: i += 1 p = x[i-1] while i < n and x[i] <= p+r: i += 1 ans += 1 print(ans)
【ポイント】
whileを使った素直な書き方が参考になります.if で分岐させようとすると,複雑になってしまいます.
Fence Repair
【入力】3
8 5 8
【出力】
34
【コード】
n = int(input()) l = sorted(list(map(int, input().split()))) ans = 0 while len(l) > 1: ans += l[0] + l[1] del l[:2] l.append(ans) l.sort() print(ans)
【ポイント】
書籍のコードについて:
最も短い2つの板を特定した後,①t=L[mii1]+L[mii2]を配列に追加する,②L[mii1]とL[mii2]を配列から除く,③mii1, mii2≠N-1なら次のステップで参照しないL[N-1]の値をL[i] (0 ≦ i < N-2)のどこかに移動させる,という3つの操作が必要です.これらは,L[mii1]に和tを上書きし,L[mii2]にL[N-1]を上書きすることで同時に達成できます.ここで,mii2=N-1でも問題なく処理ができます.しかし,mii1=N-1だと,L[mii1]=L[N-1]をtで上書きした後に,L[mii2]がL[N-1]を参照できずtが代入されてしまって問題になります.したがって,これを避けるため,mii1=N-1の場合にmii1とmii2を入れ替える処理がされています.
アルゴリズムの説明(二分木を使う方法):
バラバラにした板を,部品と呼びます.このとき,「各部品由来のコスト=部品のコスト(板の長さ)×節点の深さ」となります.これは,二分木の各葉○を部品のコスト(板の長さ)の和でバラして書けば理解できます.つまり,ある葉がA+B+Cだとすると,その上の葉にもA+B+Cが現れます.よって,各部品にかかるコストは,最も深い葉から最も上位の葉まで1回ずつ現れます.
その上で,このアルゴリズムの正しさは次のように説明できます.
まず,二分木の分岐のさせ方は色々考えられますが,何か1つ固定します.このとき,末端の葉には,各部品(のコスト)が1回ずつ現れます.あとは各部品の配置をどう決めるかですが,上の結果から,コストが最小となるとき,最も安い部品が最も深いところに配置されます.ここで,最も深い葉は,最後に2つに分割する板なので,かならずペアで現れます.よって,最も安い部品2つをこの2つの葉に対応させれば良いことがわかりました.ここまでで,最後に分割される2部品が決定したわけですが,この2部品を分割する前のステップに戻れば,1つ部品が減っただけで同じ問題になります.よって,また安い2部品を見つけて同じ処理をすればよいのです.さらに,ここまでの考え方は,はじめに固定した「二分木の分岐のさせ方」には依存しないのです!
アルゴリズムの説明(二分木を使わない方法):
二分木を使わなくても,簡単に説明できそうだと思って考えたのですが,ちょっと思いつきませんでした.そのうち,考えてみたいです(忘れるパターン).