Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 148 additions & 0 deletions Algorithm/DynamicProgramming/atCoder/SubsetSumProblem/B18/B18.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// ありがとうございます。Go実装で以下のようなエラーが出た場合:

// ```
// # atcoder.jp/golang
// ./main.go:38:10: st declared and not used
// ```

// これは **Goでは変数を使わないとコンパイルエラーになる** というルールによるものです。

// ---

// ## ✅ 問題のある該当行:

// ```go
// for s, st := range dp {
// newSum := s + A[i]
// ...
// }
// ```

// ここで `st` を宣言しておきながら使用していないため、Goのコンパイラが怒ります。

// ---

// ## ✅ 修正方法:`_`(ブランク識別子)で未使用変数を明示

// ```go
// for s := range dp {
// newSum := s + A[i]
// ...
// }
// ```

// または、`st` を実際に使うようにしてもOKです(ここでは使っていないので削除でOK)。

// ---

// ## ✅ 修正済みの完全版コード(Go 1.20.6対応)

// ```go
package main

import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)

// 経路復元のための構造体
type State struct {
idx int // 使用したカードのインデックス
prev int // そのときの前の合計値
}

// main関数:標準入力を読み取り、部分和の存在判定と経路出力を行う
func main() {
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
header := strings.Split(scanner.Text(), " ")
N, _ := strconv.Atoi(header[0])
S, _ := strconv.Atoi(header[1])

scanner.Scan()
parts := strings.Split(scanner.Text(), " ")
A := make([]int, N)
for i := 0; i < N; i++ {
A[i], _ = strconv.Atoi(parts[i])
}

// dp[sum] = State:sumを作るのにどのカードを使ったかとその前のsumを記録
dp := make(map[int]State)
dp[0] = State{-1, -1} // 0は何も使わずに作れる

for i := 0; i < N; i++ {
nextDP := make(map[int]State)
for s := range dp { // ✅ 未使用変数stを削除
newSum := s + A[i]
if newSum <= S {
if _, exists := dp[newSum]; !exists {
nextDP[newSum] = State{i, s}
}
}
}
for k, v := range nextDP {
dp[k] = v
}
}

if _, ok := dp[S]; !ok {
fmt.Println(-1)
return
}

// 経路復元
res := []int{}
curr := S
for curr != 0 {
state := dp[curr]
res = append(res, state.idx+1) // 1-indexed
curr = state.prev
}

// 逆順にして出力
for i, j := 0, len(res)-1; i < j; i, j = i+1, j-1 {
res[i], res[j] = res[j], res[i]
}

fmt.Println(len(res))
for i, v := range res {
if i > 0 {
fmt.Print(" ")
}
fmt.Print(v)
}
fmt.Println()
}
// ```

// ---

// ## ✅ 補足

// * Goは非常に厳格で、「未使用変数があるだけでビルドエラー」になります。
// * 使わない場合は `_` を使う、または削除が必要です。

// ---

// ✅ 解法の特徴
// 動的計画法(DP)で到達可能な合計を管理

// 経路復元のために map[int]struct{idx, prev int} を使用

// スパースDPにより、時間・空間効率を改善

// ✅ 処理時間・メモリ消費量
// 項目 説明
// 時間計算量 O(N * S) 最大 60×10000 = 600,000
// 空間計算量 O(S)(dp は map[int]State として管理)
// 最大メモリ目安 8バイト × S程度 + mapのオーバーヘッド
// Goバージョン Go 1.20.6(型推論 + map使用)

// ✅ 補足:型の明示
// dp は map[int]State 型で、各合計値に対して使ったカードとその直前の合計を記録

// State 構造体で経路復元を正確に可能にしています

102 changes: 102 additions & 0 deletions Algorithm/DynamicProgramming/atCoder/SubsetSumProblem/B18/B18.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// 以下は、先ほどのPythonコードと**同じロジック**(DP + 経路復元)で書いた **JavaScript版の解答**です。

// * `fs` を使って標準入力を読み取り、
// * 実行時間・メモリに配慮した設計になっています(辞書ベースの1次元DP)。

// ---

// ### ✅ JavaScriptコード(Node.js 18.16.1 対応)

// ```javascript
const fs = require('fs');

/**
* 与えられたカードの数列から、合計Sとなる部分集合が存在するか判定し、
* 存在する場合はカードのインデックスを1つ返す。
* @returns {void}
*/
function main() {
const input = fs.readFileSync('/dev/stdin', 'utf8').trim().split(/\s+/).map(Number);

const N = input[0]; // カードの枚数
const S = input[1]; // 目標の合計
const A = input.slice(2); // 各カードに書かれた値(長さN)

/**
* dp[s] = [i, prevSum] : 合計sは、カードi(0-indexed)を使って、prevSumから作った
* @type {Map<number, [number, number]>}
*/
const dp = new Map();
dp.set(0, null); // 合計0は何も使わずに作れる

for (let i = 0; i < N; i++) {
const nextDp = new Map(dp); // 現在の状態をコピー

for (const [s, val] of dp.entries()) {
const newSum = s + A[i];
if (newSum <= S && !nextDp.has(newSum)) {
nextDp.set(newSum, [i, s]); // s+A[i] は カードi で作った
}
}

// 更新
for (const [key, val] of nextDp.entries()) {
dp.set(key, val);
}
}

// 合計Sを作れるかチェック
if (!dp.has(S)) {
console.log(-1);
return;
}

// 経路復元
/** @type {number[]} */
const result = [];
let currSum = S;

while (currSum !== 0) {
const [i, prevSum] = dp.get(currSum);
result.push(i + 1); // 1-indexed に変換
currSum = prevSum;
}

result.reverse();
console.log(result.length);
console.log(result.join(' '));
}

main();

// ### ✅ 使用技術・仕様の要点

// | 項目 | 内容 |
// | ------ | ---------------------------------- |
// | 実行環境 | Node.js 18.16.1 |
// | 入力取得方法 | `fs.readFileSync('/dev/stdin')` |
// | 時間計算量 | `O(N*S)` (最大60万) |
// | 空間計算量 | `O(S)`(Mapに合計値を格納) |
// | 出力形式 | 条件を満たすカード枚数、カード番号(1-indexed) |
// | 経路復元 | Mapに `(カード番号, 前の合計値)` を記録し、正確な復元可能 |

// ---

// ### ✅ 動作例

// #### 入力:

// ```
// 3 7
// 2 2 3
// ```

// #### 出力:

// ```
// 3
// 1 2 3
// ```

// ---

109 changes: 109 additions & 0 deletions Algorithm/DynamicProgramming/atCoder/SubsetSumProblem/B18/B18.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<!-- 以下は、**PHP 8.2.8** に対応した、**部分和問題(Subset Sum with recovery)** の解法です。
Python / TypeScript と同様の **動的計画法 + 経路復元** の方針に基づきます。

---

## ✅ ポイント

* 型を明示しつつ、スパースなDPを `array` で構築
* 経路復元のために `dp[$sum] = [$index, $prev_sum]` を保持
* 処理時間とメモリに配慮(不要な合計値は保持しない)

---

## ✅ PHP 8.2.8 コード(fs不要)

<?php
/**
* 合計Sとなる部分集合が存在するか判定し、
* 存在する場合はそのカードのインデックスを1つ返す。
* 存在しない場合は -1 を出力する。
*
* @return void
*/
function main(): void {
// 入力読み取り
[$N, $S] = array_map('intval', explode(' ', trim(fgets(STDIN))));
$A = array_map('intval', explode(' ', trim(fgets(STDIN))));

/** @var array<int, array{int, int}|null> $dp */
$dp = [];
$dp[0] = null; // 0は何も使わずに作れる

for ($i = 0; $i < $N; $i++) {
/** @var array<int, array{int, int}> $next */
$next = $dp;

foreach ($dp as $s => $val) {
$newSum = $s + $A[$i];
if ($newSum <= $S && !isset($next[$newSum])) {
$next[$newSum] = [$i, $s]; // カード$i を使って newSum を構成
}
}

$dp = $next;
}

if (!isset($dp[$S])) {
echo "-1\n";
return;
}

// 経路復元
$res = [];
$curr = $S;

while ($curr !== 0) {
[$i, $prev] = $dp[$curr];
$res[] = $i + 1; // 1-indexed に変換
$curr = $prev;
}

// 出力
$res = array_reverse($res);
echo count($res) . "\n";
echo implode(' ', $res) . "\n";
}

main();
// ```

// ---

// ## ✅ 動作確認例

// ### 入力:

// ```
// 3 7
// 2 2 3
// ```

// ### 出力:

// ```
// 3
// 1 2 3
// ```

// ---

// ## ✅ 時間・メモリ計算量

// | 項目 | 計算量 | 備考 |
// | -------- | -------- | -------------- |
// | 時間計算量 | O(N × S) | 最大約 60万 |
// | 空間計算量 | O(S) | `dp` 配列はスパース構造 |
// | PHPバージョン | 8.2.8 | タイプヒント使用 |

// ---

// ## ✅ 型の明示について

// PHPではスカラー型を直接 `array` のキーや値に型付けできませんが、PHPDoc で以下のように記述しています:

// ```php
// /** @var array<int, array{int, int}|null> $dp */
// ```

// これは、「`int` をキーとし、値は `[$index, $prev_sum]` のペア、または null」と明示しています。
Loading