From 4abfced5625a3b725de78bcfc64430e64b5ae93a Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Tue, 15 Jul 2025 21:59:21 +0900 Subject: [PATCH] atcoder B21 - Longest Subpalindrome DP --- .../DynamicProgramming/atCoder/B21/B21.go | 107 +++++++++++ .../DynamicProgramming/atCoder/B21/B21.js | 91 +++++++++ .../DynamicProgramming/atCoder/B21/B21.php | 96 ++++++++++ .../DynamicProgramming/atCoder/B21/B21.py | 90 +++++++++ .../DynamicProgramming/atCoder/B21/B21.ts | 71 +++++++ .../DynamicProgramming/atCoder/B21/README.md | 174 ++++++++++++++++++ 6 files changed, 629 insertions(+) create mode 100644 Algorithm/DynamicProgramming/atCoder/B21/B21.go create mode 100644 Algorithm/DynamicProgramming/atCoder/B21/B21.js create mode 100644 Algorithm/DynamicProgramming/atCoder/B21/B21.php create mode 100644 Algorithm/DynamicProgramming/atCoder/B21/B21.py create mode 100644 Algorithm/DynamicProgramming/atCoder/B21/B21.ts create mode 100644 Algorithm/DynamicProgramming/atCoder/B21/README.md diff --git a/Algorithm/DynamicProgramming/atCoder/B21/B21.go b/Algorithm/DynamicProgramming/atCoder/B21/B21.go new file mode 100644 index 00000000..2a6ca3c7 --- /dev/null +++ b/Algorithm/DynamicProgramming/atCoder/B21/B21.go @@ -0,0 +1,107 @@ +// 以下に、**Go (go 1.20.6)** を用いて「最長回文部分列の長さ(Longest Palindromic Subsequence)」を求める実装を提示します。 +// **型を明示**し、\*\*処理時間・メモリ制約(1秒・1024MiB)\*\*を満たすよう設計しています。 + +// --- + +// ## ✅ 実装(Go) + +// ```go +package main + +import ( + "bufio" + "fmt" + "os" + "strings" +) + +// longestPalindromicSubsequence は、与えられた文字列 S に対して、削除のみで構成可能な +// 最長の回文部分列の長さを返します。 +// パラメータ: +// - s string : 英小文字からなる入力文字列(長さ N) +// 戻り値: +// - int : 最長の回文部分列の長さ +func longestPalindromicSubsequence(s string) int { + n := len(s) + + // dp[i][j] := s[i..j] の最長回文部分列の長さ + dp := make([][]int, n) + for i := range dp { + dp[i] = make([]int, n) + } + + // 長さ1の部分列は常に回文 + for i := 0; i < n; i++ { + dp[i][i] = 1 + } + + // 長さ2以上の部分列についてのDP + for length := 2; length <= n; length++ { + for i := 0; i <= n-length; i++ { + j := i + length - 1 + if s[i] == s[j] { + if length == 2 { + dp[i][j] = 2 + } else { + dp[i][j] = dp[i+1][j-1] + 2 + } + } else { + if dp[i+1][j] > dp[i][j-1] { + dp[i][j] = dp[i+1][j] + } else { + dp[i][j] = dp[i][j-1] + } + } + } + } + + return dp[0][n-1] +} + +func main() { + scanner := bufio.NewScanner(os.Stdin) + scanner.Scan() + // 入力 N は使用しないが、読み取りだけ行う + _ = scanner.Text() + + scanner.Scan() + s := strings.TrimSpace(scanner.Text()) + + result := longestPalindromicSubsequence(s) + fmt.Println(result) +} + +// ## ✅ 入力例と出力 + +// ### 入力: + +// ``` +// 11 +// programming +// ``` + +// ### 出力: + +// ``` +// 4 +// ``` + +// --- + +// ## ✅ 時間・メモリの分析 + +// | 項目 | 分析内容 | +// | ------ | ----------------------- | +// | 時間計算量 | O(N^2)(最大1000×1000) | +// | 空間計算量 | O(N^2)(1000×1000 int配列) | +// | メモリ使用量 | 約4〜8MB(int型が4〜8バイト) | +// | 実行時間制限 | 約1秒 → Goの速度で余裕あり | +// | メモリ制限 | 1024 MiB → 問題なし | + +// --- + +// ## ✅ 備考 + +// * 入力制約から N ≤ 1000 のため、2次元DPでも十分高速かつ低メモリです。 +// * 回文の中身を復元することも可能ですが、今回は「長さ」だけを効率的に求めます。 +// * `bufio.Scanner` を使用して高速に標準入力を処理しています。 diff --git a/Algorithm/DynamicProgramming/atCoder/B21/B21.js b/Algorithm/DynamicProgramming/atCoder/B21/B21.js new file mode 100644 index 00000000..1ed5795d --- /dev/null +++ b/Algorithm/DynamicProgramming/atCoder/B21/B21.js @@ -0,0 +1,91 @@ +// 以下に、**最長部分回文部分列(Longest Palindromic Subsequence)** を求める JavaScript (Node.js) のコードを、`fs` モジュールを使って記述します。 + +// --- + +// ### ✅ アルゴリズム概要: + +// * 動的計画法(DP)を用いて、部分文字列 `S[i..j]` の中で作れる最長回文部分列の長さを `dp[i][j]` に記録していきます。 +// * `dp[i][j]` の計算式: + +// * `S[i] === S[j]` のとき: `dp[i][j] = dp[i+1][j-1] + 2` +// * それ以外: `dp[i][j] = max(dp[i+1][j], dp[i][j-1])` + +// --- + +// ### ✅ 実装(Node.js, `fs` 使用) + +// ```javascript +const fs = require("fs"); + +/** + * 最長回文部分列の長さを返す関数 + * @param {string} S - 対象の文字列(英小文字) + * @returns {number} - 最長の回文部分列の長さ + */ +function longestPalindromicSubsequence(S) { + const N = S.length; + const dp = Array.from({ length: N }, () => Array(N).fill(0)); + + // 1文字は必ず回文 + for (let i = 0; i < N; i++) { + dp[i][i] = 1; + } + + // 長さ2以上の部分列に対するDP + for (let len = 2; len <= N; len++) { + for (let i = 0; i <= N - len; i++) { + const j = i + len - 1; + if (S[i] === S[j]) { + dp[i][j] = (len === 2) ? 2 : dp[i + 1][j - 1] + 2; + } else { + dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]); + } + } + } + + return dp[0][N - 1]; +} + +// 入力読み取り +const input = fs.readFileSync("/dev/stdin", "utf8").trim().split("\n"); +const N = Number(input[0]); +const S = input[1]; + +// 出力 +console.log(longestPalindromicSubsequence(S)); + +// ### ✅ 入力例での動作確認 + +// #### 入力: + +// ``` +// 11 +// programming +// ``` + +// #### 出力: + +// ``` +// 4 +// ``` + +// #### 入力: + +// ``` +// 7 +// abcdcba +// ``` + +// #### 出力: + +// ``` +// 7 +// ``` + +// --- + +// ### ✅ 時間・メモリ計算 + +// * **時間計算量**:O(N^2) (N ≦ 1000なので最大で約 1,000,000 ループ) +// * **空間計算量**:O(N^2) (1000×1000 の 2次元配列 → 約 8MB) + diff --git a/Algorithm/DynamicProgramming/atCoder/B21/B21.php b/Algorithm/DynamicProgramming/atCoder/B21/B21.php new file mode 100644 index 00000000..40d9d350 --- /dev/null +++ b/Algorithm/DynamicProgramming/atCoder/B21/B21.php @@ -0,0 +1,96 @@ +