Skip to content

Commit c16eed4

Browse files
committed
atcoder B18 - Subset Sum with Restoration DP Subset Sum & leetcode 13. Roman to Integer
1 parent 8b8f742 commit c16eed4

9 files changed

Lines changed: 1135 additions & 0 deletions

File tree

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
// ありがとうございます。Go実装で以下のようなエラーが出た場合:
2+
3+
// ```
4+
// # atcoder.jp/golang
5+
// ./main.go:38:10: st declared and not used
6+
// ```
7+
8+
// これは **Goでは変数を使わないとコンパイルエラーになる** というルールによるものです。
9+
10+
// ---
11+
12+
// ## ✅ 問題のある該当行:
13+
14+
// ```go
15+
// for s, st := range dp {
16+
// newSum := s + A[i]
17+
// ...
18+
// }
19+
// ```
20+
21+
// ここで `st` を宣言しておきながら使用していないため、Goのコンパイラが怒ります。
22+
23+
// ---
24+
25+
// ## ✅ 修正方法:`_`(ブランク識別子)で未使用変数を明示
26+
27+
// ```go
28+
// for s := range dp {
29+
// newSum := s + A[i]
30+
// ...
31+
// }
32+
// ```
33+
34+
// または、`st` を実際に使うようにしてもOKです(ここでは使っていないので削除でOK)。
35+
36+
// ---
37+
38+
// ## ✅ 修正済みの完全版コード(Go 1.20.6対応)
39+
40+
// ```go
41+
package main
42+
43+
import (
44+
"bufio"
45+
"fmt"
46+
"os"
47+
"strconv"
48+
"strings"
49+
)
50+
51+
// 経路復元のための構造体
52+
type State struct {
53+
idx int // 使用したカードのインデックス
54+
prev int // そのときの前の合計値
55+
}
56+
57+
// main関数:標準入力を読み取り、部分和の存在判定と経路出力を行う
58+
func main() {
59+
scanner := bufio.NewScanner(os.Stdin)
60+
scanner.Scan()
61+
header := strings.Split(scanner.Text(), " ")
62+
N, _ := strconv.Atoi(header[0])
63+
S, _ := strconv.Atoi(header[1])
64+
65+
scanner.Scan()
66+
parts := strings.Split(scanner.Text(), " ")
67+
A := make([]int, N)
68+
for i := 0; i < N; i++ {
69+
A[i], _ = strconv.Atoi(parts[i])
70+
}
71+
72+
// dp[sum] = State:sumを作るのにどのカードを使ったかとその前のsumを記録
73+
dp := make(map[int]State)
74+
dp[0] = State{-1, -1} // 0は何も使わずに作れる
75+
76+
for i := 0; i < N; i++ {
77+
nextDP := make(map[int]State)
78+
for s := range dp { // ✅ 未使用変数stを削除
79+
newSum := s + A[i]
80+
if newSum <= S {
81+
if _, exists := dp[newSum]; !exists {
82+
nextDP[newSum] = State{i, s}
83+
}
84+
}
85+
}
86+
for k, v := range nextDP {
87+
dp[k] = v
88+
}
89+
}
90+
91+
if _, ok := dp[S]; !ok {
92+
fmt.Println(-1)
93+
return
94+
}
95+
96+
// 経路復元
97+
res := []int{}
98+
curr := S
99+
for curr != 0 {
100+
state := dp[curr]
101+
res = append(res, state.idx+1) // 1-indexed
102+
curr = state.prev
103+
}
104+
105+
// 逆順にして出力
106+
for i, j := 0, len(res)-1; i < j; i, j = i+1, j-1 {
107+
res[i], res[j] = res[j], res[i]
108+
}
109+
110+
fmt.Println(len(res))
111+
for i, v := range res {
112+
if i > 0 {
113+
fmt.Print(" ")
114+
}
115+
fmt.Print(v)
116+
}
117+
fmt.Println()
118+
}
119+
// ```
120+
121+
// ---
122+
123+
// ## ✅ 補足
124+
125+
// * Goは非常に厳格で、「未使用変数があるだけでビルドエラー」になります。
126+
// * 使わない場合は `_` を使う、または削除が必要です。
127+
128+
// ---
129+
130+
// ✅ 解法の特徴
131+
// 動的計画法(DP)で到達可能な合計を管理
132+
133+
// 経路復元のために map[int]struct{idx, prev int} を使用
134+
135+
// スパースDPにより、時間・空間効率を改善
136+
137+
// ✅ 処理時間・メモリ消費量
138+
// 項目 説明
139+
// 時間計算量 O(N * S) 最大 60×10000 = 600,000
140+
// 空間計算量 O(S)(dp は map[int]State として管理)
141+
// 最大メモリ目安 8バイト × S程度 + mapのオーバーヘッド
142+
// Goバージョン Go 1.20.6(型推論 + map使用)
143+
144+
// ✅ 補足:型の明示
145+
// dp は map[int]State 型で、各合計値に対して使ったカードとその直前の合計を記録
146+
147+
// State 構造体で経路復元を正確に可能にしています
148+
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// 以下は、先ほどのPythonコードと**同じロジック**(DP + 経路復元)で書いた **JavaScript版の解答**です。
2+
3+
// * `fs` を使って標準入力を読み取り、
4+
// * 実行時間・メモリに配慮した設計になっています(辞書ベースの1次元DP)。
5+
6+
// ---
7+
8+
// ### ✅ JavaScriptコード(Node.js 18.16.1 対応)
9+
10+
// ```javascript
11+
const fs = require('fs');
12+
13+
/**
14+
* 与えられたカードの数列から、合計Sとなる部分集合が存在するか判定し、
15+
* 存在する場合はカードのインデックスを1つ返す。
16+
* @returns {void}
17+
*/
18+
function main() {
19+
const input = fs.readFileSync('/dev/stdin', 'utf8').trim().split(/\s+/).map(Number);
20+
21+
const N = input[0]; // カードの枚数
22+
const S = input[1]; // 目標の合計
23+
const A = input.slice(2); // 各カードに書かれた値(長さN)
24+
25+
/**
26+
* dp[s] = [i, prevSum] : 合計sは、カードi(0-indexed)を使って、prevSumから作った
27+
* @type {Map<number, [number, number]>}
28+
*/
29+
const dp = new Map();
30+
dp.set(0, null); // 合計0は何も使わずに作れる
31+
32+
for (let i = 0; i < N; i++) {
33+
const nextDp = new Map(dp); // 現在の状態をコピー
34+
35+
for (const [s, val] of dp.entries()) {
36+
const newSum = s + A[i];
37+
if (newSum <= S && !nextDp.has(newSum)) {
38+
nextDp.set(newSum, [i, s]); // s+A[i] は カードi で作った
39+
}
40+
}
41+
42+
// 更新
43+
for (const [key, val] of nextDp.entries()) {
44+
dp.set(key, val);
45+
}
46+
}
47+
48+
// 合計Sを作れるかチェック
49+
if (!dp.has(S)) {
50+
console.log(-1);
51+
return;
52+
}
53+
54+
// 経路復元
55+
/** @type {number[]} */
56+
const result = [];
57+
let currSum = S;
58+
59+
while (currSum !== 0) {
60+
const [i, prevSum] = dp.get(currSum);
61+
result.push(i + 1); // 1-indexed に変換
62+
currSum = prevSum;
63+
}
64+
65+
result.reverse();
66+
console.log(result.length);
67+
console.log(result.join(' '));
68+
}
69+
70+
main();
71+
72+
// ### ✅ 使用技術・仕様の要点
73+
74+
// | 項目 | 内容 |
75+
// | ------ | ---------------------------------- |
76+
// | 実行環境 | Node.js 18.16.1 |
77+
// | 入力取得方法 | `fs.readFileSync('/dev/stdin')` |
78+
// | 時間計算量 | `O(N*S)` (最大60万) |
79+
// | 空間計算量 | `O(S)`(Mapに合計値を格納) |
80+
// | 出力形式 | 条件を満たすカード枚数、カード番号(1-indexed) |
81+
// | 経路復元 | Mapに `(カード番号, 前の合計値)` を記録し、正確な復元可能 |
82+
83+
// ---
84+
85+
// ### ✅ 動作例
86+
87+
// #### 入力:
88+
89+
// ```
90+
// 3 7
91+
// 2 2 3
92+
// ```
93+
94+
// #### 出力:
95+
96+
// ```
97+
// 3
98+
// 1 2 3
99+
// ```
100+
101+
// ---
102+
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<!-- 以下は、**PHP 8.2.8** に対応した、**部分和問題(Subset Sum with recovery)** の解法です。
2+
Python / TypeScript と同様の **動的計画法 + 経路復元** の方針に基づきます。
3+
4+
---
5+
6+
## ✅ ポイント
7+
8+
* 型を明示しつつ、スパースなDPを `array` で構築
9+
* 経路復元のために `dp[$sum] = [$index, $prev_sum]` を保持
10+
* 処理時間とメモリに配慮(不要な合計値は保持しない)
11+
12+
---
13+
14+
## ✅ PHP 8.2.8 コード(fs不要)
15+
16+
<?php
17+
/**
18+
* 合計Sとなる部分集合が存在するか判定し、
19+
* 存在する場合はそのカードのインデックスを1つ返す。
20+
* 存在しない場合は -1 を出力する。
21+
*
22+
* @return void
23+
*/
24+
function main(): void {
25+
// 入力読み取り
26+
[$N, $S] = array_map('intval', explode(' ', trim(fgets(STDIN))));
27+
$A = array_map('intval', explode(' ', trim(fgets(STDIN))));
28+
29+
/** @var array<int, array{int, int}|null> $dp */
30+
$dp = [];
31+
$dp[0] = null; // 0は何も使わずに作れる
32+
33+
for ($i = 0; $i < $N; $i++) {
34+
/** @var array<int, array{int, int}> $next */
35+
$next = $dp;
36+
37+
foreach ($dp as $s => $val) {
38+
$newSum = $s + $A[$i];
39+
if ($newSum <= $S && !isset($next[$newSum])) {
40+
$next[$newSum] = [$i, $s]; // カード$i を使って newSum を構成
41+
}
42+
}
43+
44+
$dp = $next;
45+
}
46+
47+
if (!isset($dp[$S])) {
48+
echo "-1\n";
49+
return;
50+
}
51+
52+
// 経路復元
53+
$res = [];
54+
$curr = $S;
55+
56+
while ($curr !== 0) {
57+
[$i, $prev] = $dp[$curr];
58+
$res[] = $i + 1; // 1-indexed に変換
59+
$curr = $prev;
60+
}
61+
62+
// 出力
63+
$res = array_reverse($res);
64+
echo count($res) . "\n";
65+
echo implode(' ', $res) . "\n";
66+
}
67+
68+
main();
69+
// ```
70+
71+
// ---
72+
73+
// ## ✅ 動作確認例
74+
75+
// ### 入力:
76+
77+
// ```
78+
// 3 7
79+
// 2 2 3
80+
// ```
81+
82+
// ### 出力:
83+
84+
// ```
85+
// 3
86+
// 1 2 3
87+
// ```
88+
89+
// ---
90+
91+
// ## ✅ 時間・メモリ計算量
92+
93+
// | 項目 | 計算量 | 備考 |
94+
// | -------- | -------- | -------------- |
95+
// | 時間計算量 | O(N × S) | 最大約 60万 |
96+
// | 空間計算量 | O(S) | `dp` 配列はスパース構造 |
97+
// | PHPバージョン | 8.2.8 | タイプヒント使用 |
98+
99+
// ---
100+
101+
// ## ✅ 型の明示について
102+
103+
// PHPではスカラー型を直接 `array` のキーや値に型付けできませんが、PHPDoc で以下のように記述しています:
104+
105+
// ```php
106+
// /** @var array<int, array{int, int}|null> $dp */
107+
// ```
108+
109+
// これは、「`int` をキーとし、値は `[$index, $prev_sum]` のペア、または null」と明示しています。

0 commit comments

Comments
 (0)