Skip to content

Commit 378256e

Browse files
authored
Merge pull request #77 from myoshi2891/dev/macbook_pro
atcoder B21 - Longest Subpalindrome DP
2 parents 1fce761 + 4abfced commit 378256e

6 files changed

Lines changed: 629 additions & 0 deletions

File tree

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// 以下に、**Go (go 1.20.6)** を用いて「最長回文部分列の長さ(Longest Palindromic Subsequence)」を求める実装を提示します。
2+
// **型を明示**し、\*\*処理時間・メモリ制約(1秒・1024MiB)\*\*を満たすよう設計しています。
3+
4+
// ---
5+
6+
// ## ✅ 実装(Go)
7+
8+
// ```go
9+
package main
10+
11+
import (
12+
"bufio"
13+
"fmt"
14+
"os"
15+
"strings"
16+
)
17+
18+
// longestPalindromicSubsequence は、与えられた文字列 S に対して、削除のみで構成可能な
19+
// 最長の回文部分列の長さを返します。
20+
// パラメータ:
21+
// - s string : 英小文字からなる入力文字列(長さ N)
22+
// 戻り値:
23+
// - int : 最長の回文部分列の長さ
24+
func longestPalindromicSubsequence(s string) int {
25+
n := len(s)
26+
27+
// dp[i][j] := s[i..j] の最長回文部分列の長さ
28+
dp := make([][]int, n)
29+
for i := range dp {
30+
dp[i] = make([]int, n)
31+
}
32+
33+
// 長さ1の部分列は常に回文
34+
for i := 0; i < n; i++ {
35+
dp[i][i] = 1
36+
}
37+
38+
// 長さ2以上の部分列についてのDP
39+
for length := 2; length <= n; length++ {
40+
for i := 0; i <= n-length; i++ {
41+
j := i + length - 1
42+
if s[i] == s[j] {
43+
if length == 2 {
44+
dp[i][j] = 2
45+
} else {
46+
dp[i][j] = dp[i+1][j-1] + 2
47+
}
48+
} else {
49+
if dp[i+1][j] > dp[i][j-1] {
50+
dp[i][j] = dp[i+1][j]
51+
} else {
52+
dp[i][j] = dp[i][j-1]
53+
}
54+
}
55+
}
56+
}
57+
58+
return dp[0][n-1]
59+
}
60+
61+
func main() {
62+
scanner := bufio.NewScanner(os.Stdin)
63+
scanner.Scan()
64+
// 入力 N は使用しないが、読み取りだけ行う
65+
_ = scanner.Text()
66+
67+
scanner.Scan()
68+
s := strings.TrimSpace(scanner.Text())
69+
70+
result := longestPalindromicSubsequence(s)
71+
fmt.Println(result)
72+
}
73+
74+
// ## ✅ 入力例と出力
75+
76+
// ### 入力:
77+
78+
// ```
79+
// 11
80+
// programming
81+
// ```
82+
83+
// ### 出力:
84+
85+
// ```
86+
// 4
87+
// ```
88+
89+
// ---
90+
91+
// ## ✅ 時間・メモリの分析
92+
93+
// | 項目 | 分析内容 |
94+
// | ------ | ----------------------- |
95+
// | 時間計算量 | O(N^2)(最大1000×1000) |
96+
// | 空間計算量 | O(N^2)(1000×1000 int配列) |
97+
// | メモリ使用量 | 約4〜8MB(int型が4〜8バイト) |
98+
// | 実行時間制限 | 約1秒 → Goの速度で余裕あり |
99+
// | メモリ制限 | 1024 MiB → 問題なし |
100+
101+
// ---
102+
103+
// ## ✅ 備考
104+
105+
// * 入力制約から N ≤ 1000 のため、2次元DPでも十分高速かつ低メモリです。
106+
// * 回文の中身を復元することも可能ですが、今回は「長さ」だけを効率的に求めます。
107+
// * `bufio.Scanner` を使用して高速に標準入力を処理しています。
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// 以下に、**最長部分回文部分列(Longest Palindromic Subsequence)** を求める JavaScript (Node.js) のコードを、`fs` モジュールを使って記述します。
2+
3+
// ---
4+
5+
// ### ✅ アルゴリズム概要:
6+
7+
// * 動的計画法(DP)を用いて、部分文字列 `S[i..j]` の中で作れる最長回文部分列の長さを `dp[i][j]` に記録していきます。
8+
// * `dp[i][j]` の計算式:
9+
10+
// * `S[i] === S[j]` のとき: `dp[i][j] = dp[i+1][j-1] + 2`
11+
// * それ以外: `dp[i][j] = max(dp[i+1][j], dp[i][j-1])`
12+
13+
// ---
14+
15+
// ### ✅ 実装(Node.js, `fs` 使用)
16+
17+
// ```javascript
18+
const fs = require("fs");
19+
20+
/**
21+
* 最長回文部分列の長さを返す関数
22+
* @param {string} S - 対象の文字列(英小文字)
23+
* @returns {number} - 最長の回文部分列の長さ
24+
*/
25+
function longestPalindromicSubsequence(S) {
26+
const N = S.length;
27+
const dp = Array.from({ length: N }, () => Array(N).fill(0));
28+
29+
// 1文字は必ず回文
30+
for (let i = 0; i < N; i++) {
31+
dp[i][i] = 1;
32+
}
33+
34+
// 長さ2以上の部分列に対するDP
35+
for (let len = 2; len <= N; len++) {
36+
for (let i = 0; i <= N - len; i++) {
37+
const j = i + len - 1;
38+
if (S[i] === S[j]) {
39+
dp[i][j] = (len === 2) ? 2 : dp[i + 1][j - 1] + 2;
40+
} else {
41+
dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]);
42+
}
43+
}
44+
}
45+
46+
return dp[0][N - 1];
47+
}
48+
49+
// 入力読み取り
50+
const input = fs.readFileSync("/dev/stdin", "utf8").trim().split("\n");
51+
const N = Number(input[0]);
52+
const S = input[1];
53+
54+
// 出力
55+
console.log(longestPalindromicSubsequence(S));
56+
57+
// ### ✅ 入力例での動作確認
58+
59+
// #### 入力:
60+
61+
// ```
62+
// 11
63+
// programming
64+
// ```
65+
66+
// #### 出力:
67+
68+
// ```
69+
// 4
70+
// ```
71+
72+
// #### 入力:
73+
74+
// ```
75+
// 7
76+
// abcdcba
77+
// ```
78+
79+
// #### 出力:
80+
81+
// ```
82+
// 7
83+
// ```
84+
85+
// ---
86+
87+
// ### ✅ 時間・メモリ計算
88+
89+
// * **時間計算量**:O(N^2) (N ≦ 1000なので最大で約 1,000,000 ループ)
90+
// * **空間計算量**:O(N^2) (1000×1000 の 2次元配列 → 約 8MB)
91+
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<!-- 以下に、**PHP 8.2.8** を用いた「最長回文部分列の長さ(Longest Palindromic Subsequence)」を求めるコードを提示します。
2+
**型指定・関数化・処理時間とメモリ効率を考慮**した実装です。
3+
4+
---
5+
6+
## ✅ PHPコード(標準入力+DPでの最長回文部分列)
7+
8+
<?php
9+
10+
declare(strict_types=1);
11+
12+
/**
13+
* 与えられた文字列 S に対し、削除のみで構成可能な最長回文部分列の長さを求める。
14+
*
15+
* @param string $s 入力文字列(英小文字のみ)
16+
* @return int 最長回文部分列の長さ
17+
*/
18+
function longestPalindromicSubsequence(string $s): int
19+
{
20+
$n = strlen($s);
21+
22+
// dp[i][j] := S[i..j]の最長回文部分列の長さ
23+
$dp = array_fill(0, $n, array_fill(0, $n, 0));
24+
25+
// 長さ1の部分列は常に回文(自己一致)
26+
for ($i = 0; $i < $n; $i++) {
27+
$dp[$i][$i] = 1;
28+
}
29+
30+
// 部分列の長さ2からnまでのDP
31+
for ($length = 2; $length <= $n; $length++) {
32+
for ($i = 0; $i <= $n - $length; $i++) {
33+
$j = $i + $length - 1;
34+
35+
if ($s[$i] === $s[$j]) {
36+
$dp[$i][$j] = ($length === 2) ? 2 : $dp[$i + 1][$j - 1] + 2;
37+
} else {
38+
$dp[$i][$j] = max($dp[$i + 1][$j], $dp[$i][$j - 1]);
39+
}
40+
}
41+
}
42+
43+
return $dp[0][$n - 1];
44+
}
45+
46+
// 標準入力からの読み取り処理(複数行)
47+
$input = explode("\n", trim(file_get_contents("php://stdin")));
48+
$n = (int)$input[0];
49+
$s = $input[1];
50+
51+
// 関数呼び出しと出力
52+
$result = longestPalindromicSubsequence($s);
53+
echo $result . PHP_EOL;
54+
55+
// ## ✅ 入力例と出力
56+
57+
// ### 入力:
58+
59+
// ```
60+
// 11
61+
// programming
62+
// ```
63+
64+
// ### 出力:
65+
66+
// ```
67+
// 4
68+
// ```
69+
70+
// ---
71+
72+
// ## ✅ 処理量の見積もり
73+
74+
// | 項目 | 値 |
75+
// | ------ | --------------------------- |
76+
// | 時間計算量 | O(N²)(最大 1000 × 1000 = 10⁶) |
77+
// | 空間計算量 | O(N²)(1000×1000 要素)≈ 4〜8MB |
78+
// | メモリ制限 | 1024 MiB → 問題なし |
79+
// | 実行時間制限 | 1秒 → 通常の PHP 実行でも問題なし |
80+
81+
// ---
82+
83+
// ## ✅ 型とコメントに関する補足
84+
85+
// * PHP 8.2 では関数の戻り値型 (`int`) や引数型 (`string`) が厳密にサポートされています。
86+
// * `$dp` は二次元配列 `int[][]` で、PHPの動的配列構造を使用。
87+
// * 配列初期化は `array_fill()` を使って高速・省メモリ化。
88+
89+
// ---
90+
91+
// ## ✅ 追加対応可能な事項
92+
93+
// * 実際の回文を**復元して出力する拡張バージョン**
94+
// * メモリ最適化(1次元DP化)※可能だが読みづらくなる
95+
96+
// ご希望があれば続けてご提供可能です。
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# 以下に、**Python (CPython 3.11.4)** を用いて「最長回文部分列の長さ」を求める実装を提示します。
2+
# また、**型アノテーション・関数構造・処理時間およびメモリ効率**を重視しています。
3+
4+
# ---
5+
6+
# ## ✅ 実装(Python)
7+
8+
# ```python
9+
import sys
10+
11+
# 型アノテーションのインポート(任意)
12+
from typing import List
13+
14+
def longest_palindromic_subsequence(S: str) -> int:
15+
"""
16+
与えられた文字列 S に対して、削除操作のみで構成可能な最長の回文部分列の長さを返す。
17+
18+
Parameters:
19+
- S (str): 入力文字列(英小文字のみ)
20+
21+
Returns:
22+
- int: 最長回文部分列の長さ
23+
"""
24+
25+
N: int = len(S)
26+
# dp[i][j] は S[i..j] における最長回文部分列の長さ
27+
dp: List[List[int]] = [[0] * N for _ in range(N)]
28+
29+
# 長さ1の部分列は必ず回文
30+
for i in range(N):
31+
dp[i][i] = 1
32+
33+
# 長さ2以上の部分列に対するDP
34+
for length in range(2, N + 1): # 長さ 2 から N
35+
for i in range(N - length + 1):
36+
j = i + length - 1
37+
if S[i] == S[j]:
38+
if length == 2:
39+
dp[i][j] = 2
40+
else:
41+
dp[i][j] = dp[i + 1][j - 1] + 2
42+
else:
43+
dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])
44+
45+
return dp[0][N - 1]
46+
47+
# --- 入力処理 ---
48+
def main() -> None:
49+
input_lines = sys.stdin.read().splitlines()
50+
_: int = int(input_lines[0])
51+
S: str = input_lines[1]
52+
53+
result: int = longest_palindromic_subsequence(S)
54+
print(result)
55+
56+
if __name__ == "__main__":
57+
main()
58+
59+
# ## ✅ 入力例
60+
61+
# 標準入力で以下のように与えると:
62+
63+
# ```
64+
# 11
65+
# programming
66+
# ```
67+
68+
# 出力:
69+
70+
# ```
71+
# 4
72+
# ```
73+
74+
# ---
75+
76+
# ## ✅ 時間・空間計算量
77+
78+
# | 項目 | 値 | 説明 |
79+
# | ----- | -------- | ------------------- |
80+
# | 時間計算量 | `O(N^2)` | 二重ループ(最大1000×1000) |
81+
# | 空間計算量 | `O(N^2)` | 二次元リスト(最大1000×1000) |
82+
83+
# * `N = 1000` の場合でも、2次元配列は約4MB(Pythonの整数で1要素8バイト想定) → **余裕で収まります(制限1024MiB)**
84+
# * 実行時間は最大でも **100万ステップ程度**なので、CPython 3.11.4でも1秒以内で完了します。
85+
86+
# ---
87+
88+
# ## ✅ 補足:DPテーブルを1次元に圧縮することも可能?
89+
90+
# はい、理論上は可能ですが、今回のように `dp[i+1][j-1]` を参照する遷移がある場合は「全ての状態を保持」していないと正しく計算できないため、**2次元テーブルが妥当**です。

0 commit comments

Comments
 (0)