From 5239a7991bf39375482d1b1b7ecc36b9e70bf6a0 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Thu, 26 Jun 2025 23:10:14 +0900 Subject: [PATCH] atcoder A72 - Tile Painting greedy algorithm --- Algorithm/greedy algorithm/atcoder/A72/A72.go | 105 ++++++++++++ Algorithm/greedy algorithm/atcoder/A72/A72.js | 62 +++++++ .../greedy algorithm/atcoder/A72/A72.php | 74 ++++++++ Algorithm/greedy algorithm/atcoder/A72/A72.py | 59 +++++++ Algorithm/greedy algorithm/atcoder/A72/A72.ts | 66 ++++++++ .../greedy algorithm/atcoder/A72/README.md | 158 ++++++++++++++++++ 6 files changed, 524 insertions(+) create mode 100644 Algorithm/greedy algorithm/atcoder/A72/A72.go create mode 100644 Algorithm/greedy algorithm/atcoder/A72/A72.js create mode 100644 Algorithm/greedy algorithm/atcoder/A72/A72.php create mode 100644 Algorithm/greedy algorithm/atcoder/A72/A72.py create mode 100644 Algorithm/greedy algorithm/atcoder/A72/A72.ts create mode 100644 Algorithm/greedy algorithm/atcoder/A72/README.md diff --git a/Algorithm/greedy algorithm/atcoder/A72/A72.go b/Algorithm/greedy algorithm/atcoder/A72/A72.go new file mode 100644 index 00000000..38e34b27 --- /dev/null +++ b/Algorithm/greedy algorithm/atcoder/A72/A72.go @@ -0,0 +1,105 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "sort" +) + +func max(a, b int) int { + if a > b { + return a + } + return b +} + +func main() { + sc := bufio.NewScanner(os.Stdin) + sc.Split(bufio.ScanLines) + + sc.Scan() + var H, W, K int + fmt.Sscanf(sc.Text(), "%d %d %d", &H, &W, &K) + + grid := make([][]rune, H) + for i := 0; i < H; i++ { + sc.Scan() + row := []rune(sc.Text()) + grid[i] = row + } + + maxBlack := 0 + + // 全ての行選択パターンをビット列で列挙 + for bit := 0; bit < (1 << H); bit++ { + paintedRows := []int{} + for i := 0; i < H; i++ { + if (bit>>i)&1 == 1 { + paintedRows = append(paintedRows, i) + } + } + + if len(paintedRows) > K { + continue + } + + // gridのコピー(deep copy) + painted := make([][]rune, H) + for i := 0; i < H; i++ { + painted[i] = make([]rune, W) + copy(painted[i], grid[i]) + } + + // 選んだ行を黒く塗る + for _, row := range paintedRows { + for j := 0; j < W; j++ { + painted[row][j] = '#' + } + } + + // 各列の黒マス数を数える + blackCount := make([]int, W) + for j := 0; j < W; j++ { + for i := 0; i < H; i++ { + if painted[i][j] == '#' { + blackCount[j]++ + } + } + } + + // gain を計算(白マスの多い列を貪欲に選ぶ) + type gainCol struct { + gain int + col int + } + gains := []gainCol{} + for j := 0; j < W; j++ { + gains = append(gains, gainCol{H - blackCount[j], j}) + } + sort.Slice(gains, func(i, j int) bool { + return gains[i].gain > gains[j].gain + }) + + // 残りの K - 行数 だけ列を塗る + columnsToPaint := make(map[int]bool) + remain := K - len(paintedRows) + for i := 0; i < remain && i < len(gains); i++ { + columnsToPaint[gains[i].col] = true + } + + // 黒マスの総数を数える + total := 0 + for i := 0; i < H; i++ { + for j := 0; j < W; j++ { + if painted[i][j] == '#' || columnsToPaint[j] { + total++ + } + } + } + + maxBlack = max(maxBlack, total) + } + + fmt.Println(maxBlack) +} diff --git a/Algorithm/greedy algorithm/atcoder/A72/A72.js b/Algorithm/greedy algorithm/atcoder/A72/A72.js new file mode 100644 index 00000000..c3cc0dd8 --- /dev/null +++ b/Algorithm/greedy algorithm/atcoder/A72/A72.js @@ -0,0 +1,62 @@ +const fs = require('fs'); + +const input = fs.readFileSync('/dev/stdin', 'utf8').trim().split('\n'); +const [H, W, K] = input[0].split(' ').map(Number); +const grid = input.slice(1).map(line => line.split('')); + +let maxBlack = 0; + +for (let bit = 0; bit < (1 << H); bit++) { + const paintedRows = []; + for (let i = 0; i < H; i++) { + if ((bit >> i) & 1) paintedRows.push(i); + } + const rowPaintCount = paintedRows.length; + if (rowPaintCount > K) continue; + + const painted = Array.from({ length: H }, (_, i) => + grid[i].slice() + ); + + // 行を黒く塗る + for (const row of paintedRows) { + for (let j = 0; j < W; j++) { + painted[row][j] = '#'; + } + } + + // 各列の黒マス数を数える + const blackCountPerCol = Array(W).fill(0); + for (let j = 0; j < W; j++) { + for (let i = 0; i < H; i++) { + if (painted[i][j] === '#') blackCountPerCol[j]++; + } + } + + // 残りの操作回数で列を黒く塗る(貪欲に黒マスの増加が大きいものを選ぶ) + const columnGain = []; + for (let j = 0; j < W; j++) { + const gain = H - blackCountPerCol[j]; + columnGain.push([gain, j]); + } + columnGain.sort((a, b) => b[0] - a[0]); + + const columnsToPaint = new Set(); + for (let i = 0; i < K - rowPaintCount && i < columnGain.length; i++) { + columnsToPaint.add(columnGain[i][1]); + } + + // 合計黒マス数を数える + let totalBlack = 0; + for (let i = 0; i < H; i++) { + for (let j = 0; j < W; j++) { + if (painted[i][j] === '#' || columnsToPaint.has(j)) { + totalBlack++; + } + } + } + + maxBlack = Math.max(maxBlack, totalBlack); +} + +console.log(maxBlack); diff --git a/Algorithm/greedy algorithm/atcoder/A72/A72.php b/Algorithm/greedy algorithm/atcoder/A72/A72.php new file mode 100644 index 00000000..34ee33d4 --- /dev/null +++ b/Algorithm/greedy algorithm/atcoder/A72/A72.php @@ -0,0 +1,74 @@ +> $i) & 1) { + $paintedRows[] = $i; + } + } + + $rowCount = count($paintedRows); + if ($rowCount > $K) continue; + + // グリッドのコピー + $painted = []; + foreach ($grid as $row) { + $painted[] = $row; + } + + // 行を黒く塗る + foreach ($paintedRows as $i) { + for ($j = 0; $j < $W; $j++) { + $painted[$i][$j] = '#'; + } + } + + // 各列の黒マス数をカウント + $blackCount = array_fill(0, $W, 0); + for ($j = 0; $j < $W; $j++) { + for ($i = 0; $i < $H; $i++) { + if ($painted[$i][$j] === '#') { + $blackCount[$j]++; + } + } + } + + // 残りの操作回数で列を貪欲に塗る + $gain = []; + for ($j = 0; $j < $W; $j++) { + $gain[] = ['gain' => $H - $blackCount[$j], 'col' => $j]; + } + + usort($gain, function ($a, $b) { + return $b['gain'] <=> $a['gain']; + }); + + $columnsToPaint = []; + $remainingOps = $K - $rowCount; + for ($k = 0; $k < min($remainingOps, $W); $k++) { + $columnsToPaint[$gain[$k]['col']] = true; + } + + // 黒マスを数える + $totalBlack = 0; + for ($i = 0; $i < $H; $i++) { + for ($j = 0; $j < $W; $j++) { + if ($painted[$i][$j] === '#' || isset($columnsToPaint[$j])) { + $totalBlack++; + } + } + } + + $maxBlack = max($maxBlack, $totalBlack); +} + +echo $maxBlack . "\n"; \ No newline at end of file diff --git a/Algorithm/greedy algorithm/atcoder/A72/A72.py b/Algorithm/greedy algorithm/atcoder/A72/A72.py new file mode 100644 index 00000000..26a51dba --- /dev/null +++ b/Algorithm/greedy algorithm/atcoder/A72/A72.py @@ -0,0 +1,59 @@ +import sys +from itertools import combinations + +def main(): + input = sys.stdin.read + data = input().strip().split('\n') + + H, W, K = map(int, data[0].split()) + grid = [list(row) for row in data[1:]] + + max_black = 0 + + for bit in range(1 << H): + painted_rows = [] + for i in range(H): + if (bit >> i) & 1: + painted_rows.append(i) + + row_count = len(painted_rows) + if row_count > K: + continue # 操作回数オーバー + + # 行を黒く塗ったグリッドを作る + painted = [row[:] for row in grid] # deepcopy + for i in painted_rows: + painted[i] = ['#'] * W + + # 各列の黒マス数を数える + black_count_per_col = [0] * W + for j in range(W): + for i in range(H): + if painted[i][j] == '#': + black_count_per_col[j] += 1 + + # 残りの K-row_count 回で最も gain の大きい列を選ぶ + column_gain = [] + for j in range(W): + gain = H - black_count_per_col[j] # 黒く塗れば gain だけ黒が増える + column_gain.append((gain, j)) + + column_gain.sort(reverse=True) + remaining_ops = K - row_count + columns_to_paint = set() + for i in range(min(remaining_ops, W)): + columns_to_paint.add(column_gain[i][1]) + + # 最終的な黒マス数を数える + total_black = 0 + for i in range(H): + for j in range(W): + if painted[i][j] == '#' or j in columns_to_paint: + total_black += 1 + + max_black = max(max_black, total_black) + + print(max_black) + +if __name__ == "__main__": + main() diff --git a/Algorithm/greedy algorithm/atcoder/A72/A72.ts b/Algorithm/greedy algorithm/atcoder/A72/A72.ts new file mode 100644 index 00000000..f7d5c717 --- /dev/null +++ b/Algorithm/greedy algorithm/atcoder/A72/A72.ts @@ -0,0 +1,66 @@ +import * as fs from 'fs'; + +const input = fs.readFileSync('/dev/stdin', 'utf8').trim().split('\n'); +const [H, W, K] = input[0].split(' ').map(Number); +const grid: string[][] = input.slice(1).map(line => line.split('')); + +let maxBlack = 0; + +for (let bit = 0; bit < (1 << H); bit++) { + const paintedRows: number[] = []; + for (let i = 0; i < H; i++) { + if ((bit >> i) & 1) paintedRows.push(i); + } + + const rowPaintCount = paintedRows.length; + if (rowPaintCount > K) continue; + + // 塗り操作のコピー + const painted: string[][] = grid.map(row => [...row]); + + // 行を黒に塗る + for (const row of paintedRows) { + for (let j = 0; j < W; j++) { + painted[row][j] = '#'; + } + } + + // 各列の黒マス数 + const blackCountPerCol: number[] = new Array(W).fill(0); + for (let j = 0; j < W; j++) { + for (let i = 0; i < H; i++) { + if (painted[i][j] === '#') { + blackCountPerCol[j]++; + } + } + } + + // 残り操作回数で列を貪欲に選ぶ + const columnGain: [number, number][] = []; + for (let j = 0; j < W; j++) { + const gain = H - blackCountPerCol[j]; + columnGain.push([gain, j]); + } + + columnGain.sort((a, b) => b[0] - a[0]); + + const columnsToPaint = new Set(); + const remainingCols = K - rowPaintCount; + for (let i = 0; i < Math.min(remainingCols, columnGain.length); i++) { + columnsToPaint.add(columnGain[i][1]); + } + + // 黒マス合計を計算 + let totalBlack = 0; + for (let i = 0; i < H; i++) { + for (let j = 0; j < W; j++) { + if (painted[i][j] === '#' || columnsToPaint.has(j)) { + totalBlack++; + } + } + } + + maxBlack = Math.max(maxBlack, totalBlack); +} + +console.log(maxBlack); diff --git a/Algorithm/greedy algorithm/atcoder/A72/README.md b/Algorithm/greedy algorithm/atcoder/A72/README.md new file mode 100644 index 00000000..f181bb08 --- /dev/null +++ b/Algorithm/greedy algorithm/atcoder/A72/README.md @@ -0,0 +1,158 @@ + +--- + +## 💡 問題の要点 + +* **H×W のグリッド**がある(H行W列)。 +* 各マスは **白(`.`)または黒(`#`)**。 +* あなたは「**任意の行または列**を黒く塗る」操作を **K回**までできる。 +* 目的:**黒マスの総数を最大にする**。 + +--- + +## 🧭 処理ステップの図解と説明 + +### 🧮 入力例(H=4, W=10, K=3) + +``` +入力: +4 10 3 +##...#.##. +.#....#... +##.####..# +#..######. + +グリッド(初期状態): + +行↓/列→ 0 1 2 3 4 5 6 7 8 9 + --------------------- + 0 | # # . . . # . # # . + 1 | . # . . . . # . . . + 2 | # # . # # # # . . # + 3 | # . . # # # # # # . + +``` + +--- + +### 🔄 ステップ1:すべての行の選び方を全探索(2^Hパターン) + +最大 `2^10 = 1024` 通りの\*\*「どの行を塗るかの選び方」\*\*を調べる。 + +#### 例)行\[0, 2]を塗るとする(2行塗る → K残り1回) + +``` +→ 行0と行2の全マスが強制的に黒になる + +(変更後) + +行↓/列→ 0 1 2 3 4 5 6 7 8 9 + --------------------- + 0 | # # # # # # # # # # ← 全部黒 + 1 | . # . . . . # . . . + 2 | # # # # # # # # # # ← 全部黒 + 3 | # . . # # # # # # . + +``` + +--- + +### 📊 ステップ2:各列の黒マス数をカウント + +列ごとに**何個の黒マスがあるか数える**(塗った行も含めて) + +``` +例:列別黒マス数 + +列: 0 1 2 3 4 5 6 7 8 9 + ------------------- +数: 3 3 2 3 3 3 4 3 3 2 + +(黒マスがH=4に満たない列は、黒く塗ることで増加が見込める) +``` + +--- + +### 🧠 ステップ3:残り操作数で「列」を貪欲に選んで塗る + +* 今回は **K = 3**、既に **行を2回塗ってる**ので、残り1回の操作で**どの列を塗るか?** +* 列ごとに「塗れば何マス増えるか(= H - 黒マス数)」を計算 + +``` +gain[j] = H - 黒マス数 + +列ごとの gain: +列: 0 1 2 3 4 5 6 7 8 9 +増: 1 1 2 1 1 1 0 1 1 2 + +→ gain 最大の列2 or 9 を1本だけ塗る +``` + +#### たとえば列2を選んで塗ると: + +``` +→ 全行の列2が黒くなる + +更新後: + +行↓/列→ 0 1 2 3 4 5 6 7 8 9 + --------------------- + 0 | # # # # # # # # # # + 1 | . # # . . . # . . . + 2 | # # # # # # # # # # + 3 | # . # # # # # # # . +``` + +--- + +### 🔢 ステップ4:全体の黒マスを数える + +最終盤面: + +``` +黒マス数の合計 = 各マスについて # ならカウント +→ この例では 37 +``` + +--- + +## 🔁 この手順を全行選択パターン(2^H)について繰り返す + +* 毎回、残りの K から「列の塗り方」を**貪欲法**で選ぶ。 +* 各パターンの黒マス数を比べ、**最大値を記録**。 + +--- + +## ✅ 結果 + +``` +最大の黒マス数 → 37(この例では) +``` + +--- + +## ✏️ 補足:なぜ行だけ全探索で良いの? + +* H ≤ 10 → 全ての行の選び方は **2^10 = 1024通り** +* W は最大100なので列の全探索は厳しい → **列は貪欲に最適な列だけ選ぶ** + +--- + +## 📌 図と処理の対応まとめ + +| ステップ | 処理 | 図の説明位置 | +| ----- | ------------- | ------ | +| ステップ1 | 行選択の全探索 | 🧭 | +| ステップ2 | 黒マス数カウント | 📊 | +| ステップ3 | 残りの列を貪欲で塗る | 🧠 | +| ステップ4 | 最終的な黒マス数をカウント | 🔢 | + +--- + +| [提出日時](https://atcoder.jp/contests/tessoku-book/submissions/me?desc=true&orderBy=created) | 問題 | ユーザ | 言語 | [得点](https://atcoder.jp/contests/tessoku-book/submissions/me?desc=true&orderBy=score) | [コード長](https://atcoder.jp/contests/tessoku-book/submissions/me?orderBy=source_length) | 結果 | [実行時間](https://atcoder.jp/contests/tessoku-book/submissions/me?orderBy=time_consumption) | [メモリ](https://atcoder.jp/contests/tessoku-book/submissions/me?orderBy=memory_consumption) | | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| 2025-06-26 23:04:55 | [A72 - Tile Painting](https://atcoder.jp/contests/tessoku-book/tasks/tessoku_book_bt) | [myoshizumi](https://atcoder.jp/users/myoshizumi) | [PHP (php 8.2.8)](https://atcoder.jp/contests/tessoku-book/submissions/me?f.Language=5016) | 1000 | 1813 Byte | **AC** | 104 ms | 21632 KiB | [詳細](https://atcoder.jp/contests/tessoku-book/submissions/67085480) | +| 2025-06-26 23:03:03 | [A72 - Tile Painting](https://atcoder.jp/contests/tessoku-book/tasks/tessoku_book_bt) | [myoshizumi](https://atcoder.jp/users/myoshizumi) | [Go (go 1.20.6)](https://atcoder.jp/contests/tessoku-book/submissions/me?f.Language=5002) | 1000 | 2034 Byte | **AC** | 17 ms | 6564 KiB | [詳細](https://atcoder.jp/contests/tessoku-book/submissions/67085444) | +| 2025-06-26 23:01:26 | [A72 - Tile Painting](https://atcoder.jp/contests/tessoku-book/tasks/tessoku_book_bt) | [myoshizumi](https://atcoder.jp/users/myoshizumi) | [Python (CPython 3.11.4)](https://atcoder.jp/contests/tessoku-book/submissions/me?f.Language=5055) | 1000 | 1815 Byte | **AC** | 133 ms | 9144 KiB | [詳細](https://atcoder.jp/contests/tessoku-book/submissions/67085411) | +| 2025-06-26 22:49:19 | [A72 - Tile Painting](https://atcoder.jp/contests/tessoku-book/tasks/tessoku_book_bt) | [myoshizumi](https://atcoder.jp/users/myoshizumi) | [TypeScript 5.1 (Node.js 18.16.1)](https://atcoder.jp/contests/tessoku-book/submissions/me?f.Language=5058) | 1000 | 1890 Byte | **AC** | 95 ms | 49472 KiB | [詳細](https://atcoder.jp/contests/tessoku-book/submissions/67085207) | +| 2025-06-26 22:43:39 | [A72 - Tile Painting](https://atcoder.jp/contests/tessoku-book/tasks/tessoku_book_bt) | [myoshizumi](https://atcoder.jp/users/myoshizumi) | [JavaScript (Node.js 18.16.1)](https://atcoder.jp/contests/tessoku-book/submissions/me?f.Language=5009) | 1000 | 1817 Byte | **AC** | 103 ms | 49120 KiB | [詳細](https://atcoder.jp/contests/tessoku-book/submissions/67085118) | \ No newline at end of file