Skip to content

Commit 7300ab1

Browse files
committed
atcoder B14 - Another Subset Sum 半分全列挙(Meet-in-the-middle)
1 parent c540853 commit 7300ab1

6 files changed

Lines changed: 824 additions & 0 deletions

File tree

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// 以下に、**Go (go 1.20.6)** による解答を提示します。
2+
// 制約に対応するために、**半分全列挙(Meet-in-the-middle)** を用いた効率的な実装です。
3+
// 型を明示し、処理時間・メモリ使用量を考慮しています。
4+
5+
// ## ✅ 解法概要
6+
7+
// * 与えられた整数列を **前半・後半に分割**
8+
// * 各部分の **部分集合和を全列挙**(最大 2^15 = 32,768 通り)
9+
// * 一方の部分和リストに対して **二分探索**を使用し、合計が `K` になる組を探索
10+
11+
// ## ✅ Go実装(Go 1.20.6)
12+
13+
package main
14+
15+
import (
16+
"bufio"
17+
"fmt"
18+
"os"
19+
"sort"
20+
"strconv"
21+
"strings"
22+
)
23+
24+
// getSubsetSums returns all possible subset sums of arr.
25+
func getSubsetSums(arr []int) []int {
26+
n := len(arr)
27+
res := make([]int, 0, 1<<n)
28+
for bit := 0; bit < (1 << n); bit++ {
29+
sum := 0
30+
for i := 0; i < n; i++ {
31+
if bit&(1<<i) != 0 {
32+
sum += arr[i]
33+
}
34+
}
35+
res = append(res, sum)
36+
}
37+
return res
38+
}
39+
40+
// binarySearch checks if target exists in sorted slice arr.
41+
func binarySearch(arr []int, target int) bool {
42+
left, right := 0, len(arr)-1
43+
for left <= right {
44+
mid := (left + right) / 2
45+
if arr[mid] == target {
46+
return true
47+
} else if arr[mid] < target {
48+
left = mid + 1
49+
} else {
50+
right = mid - 1
51+
}
52+
}
53+
return false
54+
}
55+
56+
func main() {
57+
scanner := bufio.NewScanner(os.Stdin)
58+
scanner.Scan()
59+
header := strings.Fields(scanner.Text())
60+
N, _ := strconv.Atoi(header[0])
61+
K, _ := strconv.Atoi(header[1])
62+
63+
scanner.Scan()
64+
fields := strings.Fields(scanner.Text())
65+
A := make([]int, N)
66+
for i := 0; i < N; i++ {
67+
A[i], _ = strconv.Atoi(fields[i])
68+
}
69+
70+
mid := N / 2
71+
left := A[:mid]
72+
right := A[mid:]
73+
74+
leftSums := getSubsetSums(left)
75+
rightSums := getSubsetSums(right)
76+
sort.Ints(rightSums)
77+
78+
for _, x := range leftSums {
79+
remain := K - x
80+
if binarySearch(rightSums, remain) {
81+
fmt.Println("Yes")
82+
return
83+
}
84+
}
85+
fmt.Println("No")
86+
}
87+
88+
// ## ✅ 入力例
89+
90+
// ```
91+
// 6 30
92+
// 5 1 18 7 2 9
93+
// ```
94+
95+
// ## ✅ 出力例
96+
97+
// ```
98+
// Yes
99+
// ```
100+
101+
// ---
102+
103+
// ## ✅ 処理時間・空間計算量
104+
105+
// | ステップ | 計算量(最悪) |
106+
// | ---------- | ------------------------ |
107+
// | 部分和全列挙 | $O(2^{15} \cdot N)$ |
108+
// | ソート | $O(2^{15} \log 2^{15})$ |
109+
// | 二分探索 × ループ | $O(2^{15} \log 2^{15})$ |
110+
// | 合計 | 約 **30万回程度の演算** → 1秒以内 ✅ |
111+
112+
// * メモリ使用量:
113+
114+
// * `leftSums` + `rightSums`:最大 32,768 × 2 ≒ **65,000 整数** → 約 **260KB**程度
115+
// * 上限(1024 MiB)に**大きく余裕あり**
116+
117+
// ---
118+
119+
// ## ✅ 特記事項(Go 1.20.6対応)
120+
121+
// * `bufio.Scanner` により高速な入力処理
122+
// * 型安全:明示的な `int` スライス使用
123+
// * 標準ライブラリのみ使用(外部依存なし)
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
2+
// * **制約**より `N ≤ 30` なので、**半分全列挙(Meet-in-the-middle)** を使って効率的に解を導きます。
3+
// * 入力は `fs.readFileSync` を使って取得します。
4+
// * 出力は `console.log` で行います。
5+
6+
// ---
7+
8+
// ### ✅ 解法:半分全列挙(Meet-in-the-middle)
9+
10+
// * 配列を2つに分割して、各部分集合和を列挙し、片方はソート・二分探索。
11+
12+
// ---
13+
14+
// ### ✅ コード
15+
16+
const fs = require('fs');
17+
18+
function getSubsetSums(arr) {
19+
const result = [];
20+
const n = arr.length;
21+
for (let bit = 0; bit < (1 << n); bit++) {
22+
let sum = 0;
23+
for (let i = 0; i < n; i++) {
24+
if (bit & (1 << i)) sum += arr[i];
25+
}
26+
result.push(sum);
27+
}
28+
return result;
29+
}
30+
31+
function binarySearch(arr, target) {
32+
let left = 0, right = arr.length - 1;
33+
while (left <= right) {
34+
const mid = (left + right) >> 1;
35+
if (arr[mid] === target) return true;
36+
if (arr[mid] < target) left = mid + 1;
37+
else right = mid - 1;
38+
}
39+
return false;
40+
}
41+
42+
function main() {
43+
const input = fs.readFileSync('/dev/stdin', 'utf8').trim().split(/\s+/).map(Number);
44+
const N = input[0];
45+
const K = input[1];
46+
const A = input.slice(2);
47+
48+
const mid = Math.floor(N / 2);
49+
const left = A.slice(0, mid);
50+
const right = A.slice(mid);
51+
52+
const leftSums = getSubsetSums(left);
53+
const rightSums = getSubsetSums(right);
54+
rightSums.sort((a, b) => a - b);
55+
56+
for (const x of leftSums) {
57+
const remain = K - x;
58+
if (binarySearch(rightSums, remain)) {
59+
console.log("Yes");
60+
return;
61+
}
62+
}
63+
64+
console.log("No");
65+
}
66+
67+
main();
68+
69+
// ### ✅ 実行例
70+
71+
// 入力:
72+
73+
// ```
74+
// 6 30
75+
// 5 1 18 7 2 9
76+
// ```
77+
78+
// 出力:
79+
80+
// ```
81+
// Yes
82+
// ```
83+
84+
// ---
85+
86+
// ### ✅ 時間・空間計算量
87+
88+
// * 時間: `O(2^(N/2) * log(2^(N/2)))` ≒ `2^15 * 15` → 高速(制限内)
89+
// * 空間: `O(2^(N/2))` → 最大 32,768 個(メモリ制限内)
90+
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<!-- 以下に、**PHP 8.2.8** に対応した解答を提示します。
2+
この解法は、**半分全列挙(Meet-in-the-middle)** を用いて、時間・メモリ制約の範囲で最適に動作します。
3+
4+
## ✅ 解法戦略(再掲)
5+
6+
* 配列を2分割して、それぞれの部分集合和を列挙(最大 $2^{15} = 32,768$ 通り)
7+
* 一方をソートし、もう一方の要素と合わせて **二分探索**で目的の値 `K` を探索
8+
9+
10+
## ✅ PHPコード(PHP 8.2.8対応・型付き風コメント付き)
11+
<?php
12+
declare(strict_types=1);
13+
14+
/**
15+
* @param array<int> $arr
16+
* @return array<int>
17+
*/
18+
function getSubsetSums(array $arr): array {
19+
$n = count($arr);
20+
$res = [];
21+
for ($bit = 0; $bit < (1 << $n); $bit++) {
22+
$sum = 0;
23+
for ($i = 0; $i < $n; $i++) {
24+
if ($bit & (1 << $i)) {
25+
$sum += $arr[$i];
26+
}
27+
}
28+
$res[] = $sum;
29+
}
30+
return $res;
31+
}
32+
33+
/**
34+
* @param array<int> $arr
35+
* @param int $target
36+
* @return bool
37+
*/
38+
function binarySearch(array $arr, int $target): bool {
39+
$left = 0;
40+
$right = count($arr) - 1;
41+
while ($left <= $right) {
42+
$mid = intdiv($left + $right, 2);
43+
if ($arr[$mid] === $target) {
44+
return true;
45+
} elseif ($arr[$mid] < $target) {
46+
$left = $mid + 1;
47+
} else {
48+
$right = $mid - 1;
49+
}
50+
}
51+
return false;
52+
}
53+
54+
function main(): void {
55+
$input = trim(file_get_contents('php://stdin'));
56+
$tokens = array_map('intval', preg_split('/\s+/', $input));
57+
58+
$N = $tokens[0];
59+
$K = $tokens[1];
60+
$A = array_slice($tokens, 2);
61+
62+
$mid = intdiv($N, 2);
63+
$left = array_slice($A, 0, $mid);
64+
$right = array_slice($A, $mid);
65+
66+
$leftSums = getSubsetSums($left);
67+
$rightSums = getSubsetSums($right);
68+
sort($rightSums);
69+
70+
foreach ($leftSums as $x) {
71+
$remain = $K - $x;
72+
if (binarySearch($rightSums, $remain)) {
73+
echo "Yes\n";
74+
return;
75+
}
76+
}
77+
echo "No\n";
78+
}
79+
80+
main();
81+
82+
// ## ✅ 入力例
83+
84+
// ```
85+
// 6 30
86+
// 5 1 18 7 2 9
87+
// ```
88+
89+
// ## ✅ 出力例
90+
91+
// ```
92+
// Yes
93+
// ```
94+
95+
// ## ✅ 時間・空間計算量
96+
97+
// | 処理 | 計算量 |
98+
// | -------- | ------------------------- |
99+
// | 部分集合和列挙 | $O(2^{N/2} \cdot N)$ |
100+
// | ソート | $O(2^{N/2} \log 2^{N/2})$ |
101+
// | 探索(×ループ) | $O(2^{N/2} \log 2^{N/2})$ |
102+
// | **合計** | 約 30 万回程度の計算 → 1秒以内OK |
103+
104+
// * 最大メモリ使用:配列2個 × $2^{15} \approx 32,768$ 要素 → **数百KB** → **1024 MiB制限に収まる**
105+
106+
// ## ✅ 注意点(PHP固有)
107+
108+
// * PHP はデフォルトで大きな整数も扱える(int型は任意精度で扱える環境も多い)
109+
// * `declare(strict_types=1)` で型安全な動作を明示
110+
// * コメントで型注釈を明示(PHP 8.2 でも `array<int>` などの DocBlock を活用)

0 commit comments

Comments
 (0)