回答編集履歴

5

誤字の修正

2018/11/15 00:52

投稿

Takahito_Ogawa
Takahito_Ogawa

スコア229

test CHANGED
@@ -132,7 +132,7 @@
132
132
 
133
133
 
134
134
 
135
- Problem(A_0, p_0) = max_(p=1, ..., p_0) Problem(100, p_0 - p)の最大価値 * (100 + p) / 100(小数点以下切り捨て)
135
+ Problem(A_0, p_0) = max_(p=1, ..., p_0) Problem(A_0, p_0 - p)の最大価値 * (100 + p) / 100(小数点以下切り捨て)
136
136
 
137
137
 
138
138
 

4

解法の解釈を追記

2018/11/15 00:52

投稿

Takahito_Ogawa
Takahito_Ogawa

スコア229

test CHANGED
@@ -128,6 +128,14 @@
128
128
 
129
129
 
130
130
 
131
+ このように動的計画法では部分問題の解についての漸化式を用いて、より少ない計算量で元の問題の解を求めることができます。今回の問題においては、部分問題の解について以下の漸化式が成立していると解釈することができます。
132
+
133
+
134
+
135
+ Problem(A_0, p_0) = max_(p=1, ..., p_0) Problem(100, p_0 - p)の最大価値 * (100 + p) / 100(小数点以下切り捨て)
136
+
137
+
138
+
131
139
  ```python
132
140
 
133
141
  A_0 = 100

3

動的計画法に基づくアルゴリズムの説明を追記

2018/11/15 00:51

投稿

Takahito_Ogawa
Takahito_Ogawa

スコア229

test CHANGED
@@ -1,3 +1,193 @@
1
+ 動的計画法に基づくアルゴリズムだと、初期ポイントを`p_0`として`(p_0 + 1) * (p_0 / 2)`回の計算で解を求めることができます。以下で、動的計画法に基づくアルゴリズムの考え方を説明します。
2
+
3
+
4
+
5
+ 例えば、初期価値を`A_0 = 100`、初期ポイントを`p_0 = 10`の場合の問題を考えましょう。この問題をProblem(100, 10)と書くことにします。
6
+
7
+
8
+
9
+ 最後に何ポイント使うかによって場合分けすると、以下の10通り考えることができます。
10
+
11
+
12
+
13
+ ケース1. 最後に1ポイントだけ使う場合、直前には9ポイント残っている
14
+
15
+ ケース2. 最後に2ポイント使う場合、直前には8ポイント残っている
16
+
17
+ ...
18
+
19
+ ケース10. 最後に10ポイント全部使う場合、直前には0ポイント残っている
20
+
21
+
22
+
23
+ 実はこれらの問題は、元の問題の部分問題の解を用いて上手く解くことができます。どういうことか見ていきましょう。
24
+
25
+
26
+
27
+ 例えば、ケース2の場合の最適解は、直前の8ポイントの最適な使い方がわかっていれば実は簡単に求められます。つまり、初期価値を`A_0 = 100`、初期ポイントを`p_0 = 8`とした問題Problem(100, 8)の解がわかっていれば、ケース2の場合の最適解は、
28
+
29
+
30
+
31
+ ケース2の最適解 = Problem(100, 8)の最大価値 * (100 + 2) / 100(小数点以下切り捨て)
32
+
33
+
34
+
35
+ として求められます。
36
+
37
+
38
+
39
+ 同様に、ケース1からケース10までの解はそれぞれ、
40
+
41
+
42
+
43
+ ケース1の最適解 = Problem(100, 9)の最大価値 * (100 + 1) / 100(小数点以下切り捨て)
44
+
45
+ ケース2の最適解 = Problem(100, 8)の最大価値 * (100 + 2) / 100(小数点以下切り捨て)
46
+
47
+ ...
48
+
49
+ ケース10の最適解 = Problem(100, 0)の最大価値 * (100 + 10) / 100(小数点以下切り捨て)
50
+
51
+
52
+
53
+ となります。
54
+
55
+ ここで、ケース10の最適解で現れるProblem(100, 0)の最大価値は、明らかに100であることに注意してください。なぜなら、Problem(100, 0)は初期価値100で与えられたポイントが0だからです。
56
+
57
+
58
+
59
+ 元の問題Problem(100, 10)の解は、ケース1からケース10の最適解のうち最も大きい価値を取るものになります。
60
+
61
+
62
+
63
+ このように、元の問題はより少ないポイントしか使えない状況下での最適解(部分問題の最適解)を用いて容易に計算できます。これを素朴に再帰を用いて実装したコードが一番下に書いてあるコードになります。
64
+
65
+
66
+
67
+ 一方で、再帰を用いずにループだけで実装することも可能です。ループだけで実装する場合は、次のように考えるとよいです。
68
+
69
+
70
+
71
+ まず、Problem(100, 0)の解は上でも述べたように100であることは自明ですね。
72
+
73
+ 次に、Problem(100, 1)の解はProblem(100, 0)の最大価値 * (100 + 1) / 100(小数点以下切り捨て)で計算できます。
74
+
75
+ 次に、Problem(100, 2)の解は
76
+
77
+
78
+
79
+ 最後に1ポイント使う場合: Problem(100, 1)の最大価値 * (100 + 1) / 100(小数点以下切り捨て)
80
+
81
+ 最後に2ポイント使う場合: Problem(100, 0)の最大価値 * (100 + 2) / 100(小数点以下切り捨て)
82
+
83
+
84
+
85
+ のうち価値がより大きくなる方です。ただし、Problem(100, 1)の最大価値とProblem(100, 0)の最大価値はすでに求まっていることに注意してください。
86
+
87
+
88
+
89
+ 次に、Problem(100, 3)の解は
90
+
91
+
92
+
93
+ 最後に1ポイント使う場合: Problem(100, 2)の最大価値 * (100 + 1) / 100(小数点以下切り捨て)
94
+
95
+ 最後に2ポイント使う場合: Problem(100, 1)の最大価値 * (100 + 2) / 100(小数点以下切り捨て)
96
+
97
+ 最後に3ポイント使う場合: Problem(100, 0)の最大価値 * (100 + 3) / 100(小数点以下切り捨て)
98
+
99
+
100
+
101
+ のうち価値が最も大きくなるものです。ただし、Problem(100, 2)の最大価値とProblem(100, 1)の最大価値とProblem(100, 0)の最大価値はすでに求まっていることに注意してください。
102
+
103
+
104
+
105
+ このように与えられたポイントが小さい部分問題から順に解を求めていくことで単純なループで元の問題の解を求めることが可能です。
106
+
107
+ ループのみ再帰なしの実装を下に挙げます。
108
+
109
+
110
+
111
+ 必要な計算回数はそれぞれ次のようになります。
112
+
113
+
114
+
115
+ Problem(100, 1): 1回
116
+
117
+ Problem(100, 2): 2回
118
+
119
+ Problem(100, 3): 3回
120
+
121
+ ...
122
+
123
+ Problem(100, 10): 10回
124
+
125
+
126
+
127
+ なので、トータル(1 + 10) * (10 / 2) = 55回の計算で元の問題の解を求めることが可能です。
128
+
129
+
130
+
131
+ ```python
132
+
133
+ A_0 = 100
134
+
135
+ p_0 = 10
136
+
137
+
138
+
139
+
140
+
141
+ def solve(A_0, p_0):
142
+
143
+ # Subproblem of this problem is defined by A_0 and p_0, each corresponds to
144
+
145
+ # the initial value of some object and the points given initially.
146
+
147
+ # Variable `solutions` holds the optimal values of subproblems as a dictionary.
148
+
149
+ # The solution of the subproblem with (A_0, 0) is apparent, that is A_0.
150
+
151
+ solutions = {(A_0, 0): A_0}
152
+
153
+ for p_0 in range(1, p_0 + 1):
154
+
155
+ # Solve the subproblem with (A_0, p_0), p_0 begins with 1
156
+
157
+ # and ends with p_0 given to the function `solve`.
158
+
159
+ max_A = A_0
160
+
161
+ for point_to_be_used in range(1, p_0 + 1):
162
+
163
+ # case division with the points to be used in the last time.
164
+
165
+ A = solutions[(A_0, p_0 - point_to_be_used)] * (100 + point_to_be_used) // 100
166
+
167
+ if A > max_A:
168
+
169
+ max_A = A
170
+
171
+ # add solution of the subproblem with (A_0, p_0)
172
+
173
+ solutions.update({(A_0, p_0): max_A})
174
+
175
+ return solutions[(A_0, p_0)]
176
+
177
+
178
+
179
+ print(solve(A_0, p_0))
180
+
181
+ ```
182
+
183
+
184
+
185
+ ---
186
+
187
+ 以下、更新前の再帰に基づく実装です。
188
+
189
+
190
+
1
191
  kichirb3さんの「小数点以下切り捨て」についての指摘を受けて改めて考え直した結果、解析的に閉じた形で解を求めることが難しいと思いました。
2
192
 
3
193
 

2

計算量を追記

2018/11/15 00:41

投稿

Takahito_Ogawa
Takahito_Ogawa

スコア229

test CHANGED
@@ -16,7 +16,7 @@
16
16
 
17
17
  説明や計算量や補足については後ほど時間があるときに追記します。
18
18
 
19
- 計算量はちゃんと確認していませんが、多分O(p_0)です。計算時間がp_0にだいたい比例します。
19
+ 計算量はちゃんと確認していませんが、多分O(p_0^2)です。計算時間がp_0^2にだいたい比例するということです
20
20
 
21
21
 
22
22
 

1

計算量を追記

2018/11/08 01:05

投稿

Takahito_Ogawa
Takahito_Ogawa

スコア229

test CHANGED
@@ -15,6 +15,8 @@
15
15
  ただし、p_0をかなり大きくした場合には、Maximum Recursion Error(最大再帰呼び出し数の上限を超える)が発生しますが、pythonの再帰の上限を変更すればエラーは起こりません。
16
16
 
17
17
  説明や計算量や補足については後ほど時間があるときに追記します。
18
+
19
+ 計算量はちゃんと確認していませんが、多分O(p_0)です。計算時間がp_0にだいたい比例します。
18
20
 
19
21
 
20
22