Skip to content

Commit efc2969

Browse files
committed
atcoder B24 - Many Boxes Longest Increasing Subsequence(LIS)問題
1 parent ab05b5c commit efc2969

6 files changed

Lines changed: 768 additions & 0 deletions

File tree

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// 以下は Go 1.20.6 を用いた解法です。
2+
// **制約(N ≤ 100,000)に対応するため、計算量 O(N log N)** のアルゴリズム(2次元LIS)を使用しています。
3+
// ソートと二分探索を使った効率的な処理で、型を明示し、関数分離も行っています。
4+
5+
// ---
6+
7+
// ## ✅ Go 実装(型明示・関数分離)
8+
9+
// ```go
10+
package main
11+
12+
import (
13+
"bufio"
14+
"fmt"
15+
"os"
16+
"sort"
17+
"strconv"
18+
"strings"
19+
)
20+
21+
// Box は各箱の構造体。縦と横を持つ。
22+
type Box struct {
23+
X int // 縦の長さ
24+
Y int // 横の長さ
25+
}
26+
27+
// maxNestingDepth は最大で何重に箱をネストできるかを求める。
28+
// @param boxes []Box - 各箱の情報
29+
// @return int - 最大ネスト数
30+
func maxNestingDepth(boxes []Box) int {
31+
// X昇順、Xが同じならY降順でソート
32+
sort.Slice(boxes, func(i, j int) bool {
33+
if boxes[i].X == boxes[j].X {
34+
return boxes[i].Y > boxes[j].Y
35+
}
36+
return boxes[i].X < boxes[j].X
37+
})
38+
39+
// Y方向のLISを求める(O(N log N))
40+
lis := make([]int, 0, len(boxes))
41+
42+
for _, box := range boxes {
43+
y := box.Y
44+
idx := lowerBound(lis, y)
45+
if idx == len(lis) {
46+
lis = append(lis, y)
47+
} else {
48+
lis[idx] = y
49+
}
50+
}
51+
52+
return len(lis)
53+
}
54+
55+
// lowerBound は slice の中で target 以上の最小のインデックスを返す。
56+
// @param slice []int - 単調増加列
57+
// @param target int - 探す値
58+
// @return int - 最左位置(lower_bound)
59+
func lowerBound(slice []int, target int) int {
60+
left, right := 0, len(slice)
61+
for left < right {
62+
mid := (left + right) / 2
63+
if slice[mid] < target {
64+
left = mid + 1
65+
} else {
66+
right = mid
67+
}
68+
}
69+
return left
70+
}
71+
72+
// main は標準入力を読み込み、最大ネスト数を出力する。
73+
func main() {
74+
scanner := bufio.NewScanner(os.Stdin)
75+
scanner.Scan()
76+
n, _ := strconv.Atoi(scanner.Text())
77+
78+
boxes := make([]Box, n)
79+
80+
for i := 0; i < n && scanner.Scan(); i++ {
81+
line := scanner.Text()
82+
parts := strings.Fields(line)
83+
x, _ := strconv.Atoi(parts[0])
84+
y, _ := strconv.Atoi(parts[1])
85+
boxes[i] = Box{X: x, Y: y}
86+
}
87+
88+
result := maxNestingDepth(boxes)
89+
fmt.Println(result)
90+
}
91+
92+
// ## ✅ 計算量とメモリ使用量
93+
94+
// | 項目 | 値 |
95+
// | ------ | ---------------------------------- |
96+
// | 時間計算量 | `O(N log N)`(ソート + LIS) |
97+
// | 空間計算量 | `O(N)`(LIS配列+入力配列) |
98+
// | メモリ使用量 | 約 10〜15MB(N ≒ 100,000 のとき) |
99+
// | 最大入力対応 | `N ≦ 100,000`、`X,Y ≦ 500,000` まで対応 |
100+
101+
// ---
102+
103+
// ## ✅ 実行例
104+
105+
// ### 入力:
106+
107+
// ```
108+
// 5
109+
// 30 50
110+
// 10 30
111+
// 40 10
112+
// 50 20
113+
// 40 60
114+
// ```
115+
116+
// ### 出力:
117+
118+
// ```
119+
// 3
120+
// ```
121+
122+
// ---
123+
124+
// ## ✅ 解法ポイント
125+
126+
// * `sort.Slice()` で X昇順・Y降順の安定ソート
127+
// * `lowerBound` による Y の LIS 構築(ネスト数=LISの長さ)
128+
// * Go のスライスを活かした効率的実装
129+
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// 以下は、**JavaScript (Node.js 18.16.1)** を使った解法です。
2+
// 標準入力は `fs` モジュールを使って読み込み、\*\*箱をネストできる最大の深さ(最大チェイン長)\*\*を求めます。
3+
4+
// ---
5+
6+
// ## ✅ 解法概要(O(N log N) 解法)
7+
8+
// この問題は **2次元のLongest Increasing Subsequence(LIS)問題**に帰着できます。
9+
10+
// 1. **X昇順**、**Y降順**でソートする(Xが同じならYが逆順)
11+
12+
// * こうすることで、`X` も `Y` も単調増加となる部分列を求めるときに、`X` が同じで `Y` だけ違う箱が同列に並ばないようにできます。
13+
// 2. `Y` だけを取り出し、LIS(Longest Increasing Subsequence)を求める
14+
15+
// ---
16+
17+
// ## ✅ コード実装
18+
19+
// ```javascript
20+
const fs = require('fs');
21+
22+
/**
23+
* 与えられた箱の配列に対して最大で何重にネストできるかを求める
24+
* @param {number[][]} boxes - 各箱の [X, Y] の配列
25+
* @returns {number} - 最大のネスト数
26+
*/
27+
function maxNestingDepth(boxes) {
28+
// 1. X昇順、Xが同じならY降順でソート
29+
boxes.sort((a, b) => {
30+
if (a[0] === b[0]) return b[1] - a[1]; // Y降順
31+
return a[0] - b[0]; // X昇順
32+
});
33+
34+
// 2. Yのみを取り出して LIS を求める
35+
const lis = []; // 単調増加部分列(パイル)
36+
37+
for (const [_, y] of boxes) {
38+
// 二分探索で挿入位置を探す
39+
let left = 0, right = lis.length;
40+
while (left < right) {
41+
const mid = (left + right) >> 1;
42+
if (lis[mid] < y) left = mid + 1;
43+
else right = mid;
44+
}
45+
46+
if (left === lis.length) {
47+
lis.push(y);
48+
} else {
49+
lis[left] = y;
50+
}
51+
}
52+
53+
return lis.length;
54+
}
55+
56+
// 標準入力の読み取り
57+
function main() {
58+
const input = fs.readFileSync('/dev/stdin', 'utf8').trim().split('\n');
59+
const N = Number(input[0]);
60+
const boxes = input.slice(1).map(line => line.split(' ').map(Number));
61+
62+
const result = maxNestingDepth(boxes);
63+
console.log(result);
64+
}
65+
66+
main();
67+
// ```
68+
69+
// ---
70+
71+
// ## ✅ 実行時間・メモリ
72+
73+
// * 時間計算量: **O(N log N)**(ソートとLISに二分探索)
74+
// * 空間計算量: **O(N)**(LIS保存用)
75+
76+
// ---
77+
78+
// ## ✅ テスト例
79+
80+
// 入力例1:
81+
82+
// ```
83+
// 5
84+
// 30 50
85+
// 10 30
86+
// 40 10
87+
// 50 20
88+
// 40 60
89+
// ```
90+
91+
// 出力:
92+
93+
// ```
94+
// 3
95+
// ```
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
<!-- 以下は PHP 8.2.8 を用いた解法です。
2+
**制約: N ≤ 100,000、各座標 ≤ 500,000** に対応できるよう、**計算量 O(N log N)**、メモリ効率にも注意しています。
3+
4+
---
5+
6+
## ✅ 解法方針(2次元LIS)
7+
8+
1. `X` 昇順、`Y` 降順にソート
9+
2. `Y` のみ取り出し、\*\*LIS(最長増加部分列)\*\*を `二分探索` によって求める
10+
11+
---
12+
13+
## ✅ PHP 実装(関数分離、型付き)
14+
15+
```php -->
16+
<?php
17+
18+
/**
19+
* 最大ネスト数を求める関数
20+
*
21+
* @param array<int, array{0: int, 1: int}> $boxes - 各箱の [X, Y] を格納した配列
22+
* @return int - 最大ネスト数
23+
*/
24+
function maxNestingDepth(array $boxes): int {
25+
// ソート: X 昇順、同じなら Y 降順
26+
usort($boxes, function ($a, $b) {
27+
if ($a[0] === $b[0]) {
28+
return $b[1] <=> $a[1]; // Y 降順
29+
}
30+
return $a[0] <=> $b[0]; // X 昇順
31+
});
32+
33+
$lis = []; // 最長増加部分列 (Y方向)
34+
35+
foreach ($boxes as [$x, $y]) {
36+
$pos = binarySearchLeft($lis, $y);
37+
if ($pos === count($lis)) {
38+
$lis[] = $y;
39+
} else {
40+
$lis[$pos] = $y;
41+
}
42+
}
43+
44+
return count($lis);
45+
}
46+
47+
/**
48+
* 二分探索(lower_bound)で挿入位置を探す
49+
*
50+
* @param array<int> $arr - 単調増加配列
51+
* @param int $target - 挿入対象の数
52+
* @return int - 挿入すべき位置(最左の >= target の位置)
53+
*/
54+
function binarySearchLeft(array $arr, int $target): int {
55+
$left = 0;
56+
$right = count($arr);
57+
58+
while ($left < $right) {
59+
$mid = intdiv($left + $right, 2);
60+
if ($arr[$mid] < $target) {
61+
$left = $mid + 1;
62+
} else {
63+
$right = $mid;
64+
}
65+
}
66+
67+
return $left;
68+
}
69+
70+
/**
71+
* メイン処理:標準入力から読み込み、最大ネスト数を出力
72+
*/
73+
function main(): void {
74+
$input = file('php://stdin', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
75+
$n = (int)$input[0];
76+
77+
$boxes = [];
78+
for ($i = 1; $i <= $n; $i++) {
79+
[$x, $y] = array_map('intval', explode(' ', $input[$i]));
80+
$boxes[] = [$x, $y];
81+
}
82+
83+
$result = maxNestingDepth($boxes);
84+
echo $result . PHP_EOL;
85+
}
86+
87+
main();
88+
// ```
89+
90+
// ---
91+
92+
// ## ✅ 処理時間・メモリ使用量
93+
94+
// | 項目 | 内容 |
95+
// | ------- | ------------------------- |
96+
// | 時間計算量 | `O(N log N)` (ソート + LIS) |
97+
// | 空間計算量 | `O(N)`(LIS用配列 + 入力配列) |
98+
// | メモリ使用量例 | ≒15〜20MB(`N=100,000` の場合) |
99+
// | 対応最大サイズ | `N ≦ 100,000` |
100+
101+
// ---
102+
103+
// ## ✅ 入力例
104+
105+
// ```
106+
// 5
107+
// 30 50
108+
// 10 30
109+
// 40 10
110+
// 50 20
111+
// 40 60
112+
// ```
113+
114+
// ### 出力:
115+
116+
// ```
117+
// 3
118+
// ```
119+
120+
// ---
121+
122+
// ## ✅ 解説補足
123+
124+
// * `usort` による `X` 昇順、`Y` 降順ソートで誤ネストを防止
125+
// * `binarySearchLeft` による効率的な LIS 構築
126+
// * PHP 8.2 の型サポート(配列要素のタプル型も docblock で明示)
127+
128+
// ---
129+
130+
// ご希望があれば、ネスト順序の復元や可視化解説も可能です。

0 commit comments

Comments
 (0)