Skip to content

Commit dc46a6b

Browse files
committed
Mathematics:Fundamentals Easy Cutting Paper Squares
1 parent b6cd27a commit dc46a6b

1 file changed

Lines changed: 289 additions & 0 deletions

File tree

Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "5d3fb360",
6+
"metadata": {},
7+
"source": [
8+
"## 1. 問題分析結果\n",
9+
"\n",
10+
"### 競技プログラミング視点(最速・最小メモリ)\n",
11+
"\n",
12+
"* 1回のカットで増える紙片は **最大でも +1 個**(「同時に複数枚を重ねて切れない」ため)。\n",
13+
"* 初期は 1 枚、最終的に必要なピース数は `n*m` 枚。\n",
14+
"* よって最小カット回数は **(n*m) - 1** で確定。\n",
15+
"\n",
16+
"### 業務開発視点(型安全・保守性・入力妥当性)\n",
17+
"\n",
18+
"* `n, m` は正の整数前提だが、不正値(0 や負数)が来る可能性まで見るなら例外を投げるのが安全。\n",
19+
"* ただし HackerRank では制約内入力が前提なので、実運用ほど厳密にしなくてもOK。\n",
20+
"\n",
21+
"### Python特有考慮(CPython 3.13)\n",
22+
"\n",
23+
"* Python の `int` は任意精度なのでオーバーフローしない。\n",
24+
"* 計算は乗算1回+減算1回の **O(1)**。I/O がボトルネックになり得る問題でもない。\n",
25+
"\n",
26+
"---\n",
27+
"\n",
28+
"## 2. 採用アルゴリズムと根拠\n",
29+
"\n",
30+
"### 結論\n",
31+
"\n",
32+
"* **最小カット数 = n*m - 1**\n",
33+
"\n",
34+
"### 根拠(数学的)\n",
35+
"\n",
36+
"* 各カットは「1つの紙片を2つに分割」する操作なので、紙片数は必ず **+1** 増える。\n",
37+
"* 1枚から `n*m` 枚にするには、増分が `n*m - 1` 必要。\n",
38+
"* 実現可能性:\n",
39+
" 例えば「縦に `n-1` 回切って `n` 本の帯」にし、その後それぞれの帯を横に `m-1` 回ずつ切る…のではなく、**帯を重ねられないので** “全体を一気に切る” 形で\n",
40+
"\n",
41+
" * 縦方向に `n-1` 回\n",
42+
" * 横方向に `m-1` 回\n",
43+
" …とやるのが誤りに見えますが、実際は「どの順で切っても最終ピース数が `n*m` である限り、必要カット数は必ず `n*m-1`」で固定です(下限=上限)。\n",
44+
"\n",
45+
"---\n",
46+
"\n",
47+
"## 3. アルゴリズム比較表\n",
48+
"\n",
49+
"| アプローチ | 時間計算量 | 空間計算量 | Python実装コスト | 可読性 | 標準ライブラリ活用 | CPython最適化 | 備考 |\n",
50+
"| ------------------ | --------- | ----- | ----------- | --- | --------- | ---------- | ----------- |\n",
51+
"| 方法A: 数式 `n*m-1` | O(1) | O(1) | 低 | ★★★ | 不要 | 適 | 正攻法・最適 |\n",
52+
"| 方法B: 切断を逐次シミュレーション | O(n*m) など | O(1)〜 | 中 | ★★☆ | 不要 | 不適 | 不要に遅い、実装も冗長 |\n",
53+
"\n",
54+
"---\n",
55+
"\n",
56+
"## 4. Python特有最適化ポイント\n",
57+
"\n",
58+
"* 乗算+減算のみ:`return n * m - 1`\n",
59+
"* 変数生成やループは不要(最速・最小メモリ)\n",
60+
"* 型ヒントで pylance 対応:`def solve(n: int, m: int) -> int`\n",
61+
"\n",
62+
"---\n",
63+
"\n",
64+
"## 5. 実装パターン(2パターン)\n",
65+
"\n",
66+
"* **solve_competitive**:HackerRank想定、最短実装\n",
67+
"* **solve_production**:入力妥当性も見る(ただし HackerRank 的には不要)\n",
68+
"\n",
69+
"---\n",
70+
"\n",
71+
"## 6. HackerRankでの回答フォーマット(solve関数を完成)\n",
72+
"\n",
73+
"```python\n",
74+
"def solve(n: int, m: int) -> int:\n",
75+
" # Competitive: constraints-valid input assumed\n",
76+
" return n * m - 1\n",
77+
"```\n",
78+
"\n",
79+
"---\n",
80+
"\n",
81+
"## 7. 検証(代表ケース)\n",
82+
"\n",
83+
"* サンプル: `n=3, m=1` → `3*1-1=2` ✅\n",
84+
"* 最小: `n=1, m=1` → `0`(カット不要)✅\n",
85+
"* 大きい値でも Python の int で安全 ✅\n"
86+
]
87+
},
88+
{
89+
"cell_type": "markdown",
90+
"id": "eb2c8894",
91+
"metadata": {},
92+
"source": [
93+
"# Cutting Paper Squares - 最小カット回数は $nm-1$\n",
94+
"\n",
95+
"## 目次(TOC)\n",
96+
"- [概要](#overview)\n",
97+
"- [アルゴリズム要点(TL;DR)](#tldr)\n",
98+
"- [図解](#figures)\n",
99+
"- [証明のスケッチ](#proof)\n",
100+
"- [計算量](#complexity)\n",
101+
"- [Python 実装](#impl)\n",
102+
"- [CPython 最適化ポイント](#cpython)\n",
103+
"- [エッジケースと検証](#edgecases)\n",
104+
"- [FAQ](#faq)\n",
105+
"\n",
106+
"---\n",
107+
"\n",
108+
"<h2 id=\"overview\">概要</h2>\n",
109+
"\n",
110+
"Mary は $n \\times m$ の紙を、$1 \\times 1$ の正方形に切り分けたい。\n",
111+
"\n",
112+
"制約は次の通り:\n",
113+
"\n",
114+
"- 1回のカットで切れるのは **紙片1枚だけ**(重ねたり折ったりできない)\n",
115+
"- カットは **紙の一辺から反対側の一辺まで**貫く直線\n",
116+
"- 目的:合計で $n \\cdot m$ 枚の $1 \\times 1$ を作るための **最小カット回数**を求める\n",
117+
"\n",
118+
"---\n",
119+
"\n",
120+
"<h2 id=\"tldr\">アルゴリズム要点(TL;DR)</h2>\n",
121+
"\n",
122+
"- 初期状態は紙片が $1$ 枚\n",
123+
"- 1回のカットで紙片は必ず **ちょうど 1 枚増える**\n",
124+
" - $1$ 枚を $2$ 枚に分割するので、増分は $+1$\n",
125+
"- 最終的に必要な紙片数は $n \\cdot m$ 枚\n",
126+
"- よって必要な増分は $(n \\cdot m) - 1$\n",
127+
"- **答え:最小カット回数は $n \\cdot m - 1$**\n",
128+
"\n",
129+
"---\n",
130+
"\n",
131+
"<h2 id=\"figures\">図解</h2>\n",
132+
"\n",
133+
"### フローチャート(数学的帰結)\n",
134+
"\n",
135+
"```mermaid\n",
136+
"flowchart TD\n",
137+
" Start[開始] --> Input[入力 n と m]\n",
138+
" Input --> Pieces[必要枚数を計算]\n",
139+
" Pieces --> Formula[式 nm-1 を適用]\n",
140+
" Formula --> Output[結果を返す]\n",
141+
"````\n",
142+
"\n",
143+
"**説明**: 入力 $n,m$ から必要ピース数 $nm$ を計算し、ピース増加が1回のカットで $+1$ である性質から $nm-1$ を返します。\n",
144+
"\n",
145+
"---\n",
146+
"\n",
147+
"### データフロー(値の流れ)\n",
148+
"\n",
149+
"```mermaid\n",
150+
"graph LR\n",
151+
" A[入力 n,m] --> B[計算 nm]\n",
152+
" B --> C[計算 nm-1]\n",
153+
" C --> D[出力]\n",
154+
"```\n",
155+
"\n",
156+
"**説明**: データは $n,m \\rightarrow nm \\rightarrow nm-1$ の順に単純変換されます。\n",
157+
"\n",
158+
"> 注: Mermaid の日本語が表示されない場合は、利用側の CSS で `.mermaid { font-family: \"Noto Sans JP\", sans-serif; }` のように和文フォントを指定してください。\n",
159+
"\n",
160+
"---\n",
161+
"\n",
162+
"<h2 id=\"proof\">証明のスケッチ</h2>\n",
163+
"\n",
164+
"### 不変条件(紙片数の変化)\n",
165+
"\n",
166+
"* 「1回のカットは紙片1枚を2枚に分割する」操作である\n",
167+
"* よって紙片数は常に\n",
168+
" $$\n",
169+
" \\text{pieces} \\leftarrow \\text{pieces} + 1\n",
170+
" $$\n",
171+
"\n",
172+
"### 基底ケース\n",
173+
"\n",
174+
"* 初期状態の紙片数は $1$\n",
175+
" $$\n",
176+
" \\text{pieces}_0 = 1\n",
177+
" $$\n",
178+
"\n",
179+
"### 目標状態\n",
180+
"\n",
181+
"* 最終的に必要な紙片数は $n \\cdot m$\n",
182+
" $$\n",
183+
" \\text{pieces}_\\text{goal} = nm\n",
184+
" $$\n",
185+
"\n",
186+
"### 帰納的な増加\n",
187+
"\n",
188+
"* $k$ 回カットしたときの紙片数は\n",
189+
" $$\n",
190+
" \\text{pieces}_k = 1 + k\n",
191+
" $$\n",
192+
"\n",
193+
"### 最小回数の導出\n",
194+
"\n",
195+
"* $\\text{pieces}_k = nm$ を満たす最小 $k$ を求める:\n",
196+
" $$\n",
197+
" 1 + k = nm\n",
198+
" $$\n",
199+
" $$\n",
200+
" k = nm - 1\n",
201+
" $$\n",
202+
"\n",
203+
"### 最適性(下限=上限)\n",
204+
"\n",
205+
"* 1回のカットで増える紙片は最大でも $1$ 枚なので、$nm-1$ 未満では $nm$ 枚に到達できない(下限)\n",
206+
"* 実際に $nm-1$ 回カットすれば必ず $nm$ 枚にできる(上限)\n",
207+
"* よって最小値は一意に $nm-1$\n",
208+
"\n",
209+
"---\n",
210+
"\n",
211+
"<h2 id=\"complexity\">計算量</h2>\n",
212+
"\n",
213+
"* 時間計算量:$O(1)$\n",
214+
"* 空間計算量:$O(1)$\n",
215+
"\n",
216+
"---\n",
217+
"\n",
218+
"<h2 id=\"impl\">Python 実装</h2>\n",
219+
"\n",
220+
"* HackerRank 形式:`solve(n, m)` を実装\n",
221+
"* Pure(副作用なし):引数から結果を返すだけ\n",
222+
"* 型注釈あり(pylance 対応)\n",
223+
"\n",
224+
"```python\n",
225+
"from __future__ import annotations\n",
226+
"\n",
227+
"def solve(n: int, m: int) -> int:\n",
228+
" \"\"\"\n",
229+
" n x m の紙を 1x1 に分割する最小カット回数を返す。\n",
230+
"\n",
231+
" 数学的事実:\n",
232+
" - 1回のカットで増える紙片は +1\n",
233+
" - 初期は 1枚\n",
234+
" - 目標は nm枚\n",
235+
" => k = nm - 1\n",
236+
" \"\"\"\n",
237+
" # 最小カット回数 = 必要ピース数 - 初期ピース数 = (n*m) - 1\n",
238+
" return n * m - 1\n",
239+
"```\n",
240+
"\n",
241+
"---\n",
242+
"\n",
243+
"<h2 id=\"cpython\">CPython 最適化ポイント</h2>\n",
244+
"\n",
245+
"* 乗算 1 回、減算 1 回のみ(ループなし)\n",
246+
"* `int` は任意精度のため、制約が大きくてもオーバーフローしない\n",
247+
"* 標準ライブラリ不要(関数呼び出しも最小限)\n",
248+
"\n",
249+
"---\n",
250+
"\n",
251+
"<h2 id=\"edgecases\">エッジケースと検証</h2>\n",
252+
"\n",
253+
"| ケース | 入力 $(n,m)$ | 期待値 | 理由 |\n",
254+
"| --- | ---------- | ------ | ------------------- |\n",
255+
"| 最小 | $(1,1)$ | $0$ | すでに $1$ 枚なのでカット不要 |\n",
256+
"| 一列 | $(n,1)$ | $n-1$ | 必要枚数が $n$ なので $n-1$ |\n",
257+
"| 一行 | $(1,m)$ | $m-1$ | 必要枚数が $m$ なので $m-1$ |\n",
258+
"| 一般 | $(n,m)$ | $nm-1$ | 1回のカットで $+1$ のため |\n",
259+
"\n",
260+
"---\n",
261+
"\n",
262+
"<h2 id=\"faq\">FAQ</h2>\n",
263+
"\n",
264+
"### Q1. 縦に $(n-1)$ 回、横に $(m-1)$ 回で合計 $(n+m-2)$ では?\n",
265+
"\n",
266+
"いいえ。この問題では **切れるのは常に紙片1枚だけ**です。\n",
267+
"\n",
268+
"例えば縦に先に切って複数の帯にした後、横に1回切っても「帯をまとめて同時に切る」ことはできません。\n",
269+
"そのため、必要な $nm$ 枚を得るには紙片の増分が合計で $nm-1$ 必須になります。\n",
270+
"\n",
271+
"### Q2. $nm-1$ 回で必ず作れるの?\n",
272+
"\n",
273+
"はい。1回カットするたびに紙片が $+1$ されるので、$nm-1$ 回で必ず $nm$ 枚に到達します。\n",
274+
"\n",
275+
"### Q3. Python の `int` で大丈夫?\n",
276+
"\n",
277+
"はい。Python の整数は任意精度なので、$nm$ が非常に大きくても安全に計算できます。\n",
278+
"\n"
279+
]
280+
}
281+
],
282+
"metadata": {
283+
"language_info": {
284+
"name": "python"
285+
}
286+
},
287+
"nbformat": 4,
288+
"nbformat_minor": 5
289+
}

0 commit comments

Comments
 (0)