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
107 changes: 107 additions & 0 deletions Algorithm/DynamicProgramming/atCoder/B21/B21.go
Original file line number Diff line number Diff line change
@@ -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` を使用して高速に標準入力を処理しています。
91 changes: 91 additions & 0 deletions Algorithm/DynamicProgramming/atCoder/B21/B21.js
Original file line number Diff line number Diff line change
@@ -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)

96 changes: 96 additions & 0 deletions Algorithm/DynamicProgramming/atCoder/B21/B21.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<!-- 以下に、**PHP 8.2.8** を用いた「最長回文部分列の長さ(Longest Palindromic Subsequence)」を求めるコードを提示します。
**型指定・関数化・処理時間とメモリ効率を考慮**した実装です。

---

## ✅ PHPコード(標準入力+DPでの最長回文部分列)

<?php

declare(strict_types=1);

/**
* 与えられた文字列 S に対し、削除のみで構成可能な最長回文部分列の長さを求める。
*
* @param string $s 入力文字列(英小文字のみ)
* @return int 最長回文部分列の長さ
*/
function longestPalindromicSubsequence(string $s): int
{
$n = strlen($s);

// dp[i][j] := S[i..j]の最長回文部分列の長さ
$dp = array_fill(0, $n, array_fill(0, $n, 0));

// 長さ1の部分列は常に回文(自己一致)
for ($i = 0; $i < $n; $i++) {
$dp[$i][$i] = 1;
}

// 部分列の長さ2からnまでのDP
for ($length = 2; $length <= $n; $length++) {
for ($i = 0; $i <= $n - $length; $i++) {
$j = $i + $length - 1;

if ($s[$i] === $s[$j]) {
$dp[$i][$j] = ($length === 2) ? 2 : $dp[$i + 1][$j - 1] + 2;
} else {
$dp[$i][$j] = max($dp[$i + 1][$j], $dp[$i][$j - 1]);
}
}
}

return $dp[0][$n - 1];
}

// 標準入力からの読み取り処理(複数行)
$input = explode("\n", trim(file_get_contents("php://stdin")));
$n = (int)$input[0];
$s = $input[1];

// 関数呼び出しと出力
$result = longestPalindromicSubsequence($s);
echo $result . PHP_EOL;

// ## ✅ 入力例と出力

// ### 入力:

// ```
// 11
// programming
// ```

// ### 出力:

// ```
// 4
// ```

// ---

// ## ✅ 処理量の見積もり

// | 項目 | 値 |
// | ------ | --------------------------- |
// | 時間計算量 | O(N²)(最大 1000 × 1000 = 10⁶) |
// | 空間計算量 | O(N²)(1000×1000 要素)≈ 4〜8MB |
// | メモリ制限 | 1024 MiB → 問題なし |
// | 実行時間制限 | 1秒 → 通常の PHP 実行でも問題なし |

// ---

// ## ✅ 型とコメントに関する補足

// * PHP 8.2 では関数の戻り値型 (`int`) や引数型 (`string`) が厳密にサポートされています。
// * `$dp` は二次元配列 `int[][]` で、PHPの動的配列構造を使用。
// * 配列初期化は `array_fill()` を使って高速・省メモリ化。

// ---

// ## ✅ 追加対応可能な事項

// * 実際の回文を**復元して出力する拡張バージョン**
// * メモリ最適化(1次元DP化)※可能だが読みづらくなる

// ご希望があれば続けてご提供可能です。
90 changes: 90 additions & 0 deletions Algorithm/DynamicProgramming/atCoder/B21/B21.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# 以下に、**Python (CPython 3.11.4)** を用いて「最長回文部分列の長さ」を求める実装を提示します。
# また、**型アノテーション・関数構造・処理時間およびメモリ効率**を重視しています。

# ---

# ## ✅ 実装(Python)

# ```python
import sys

# 型アノテーションのインポート(任意)
from typing import List

def longest_palindromic_subsequence(S: str) -> int:
"""
与えられた文字列 S に対して、削除操作のみで構成可能な最長の回文部分列の長さを返す。

Parameters:
- S (str): 入力文字列(英小文字のみ)

Returns:
- int: 最長回文部分列の長さ
"""

N: int = len(S)
# dp[i][j] は S[i..j] における最長回文部分列の長さ
dp: List[List[int]] = [[0] * N for _ in range(N)]

# 長さ1の部分列は必ず回文
for i in range(N):
dp[i][i] = 1

# 長さ2以上の部分列に対するDP
for length in range(2, N + 1): # 長さ 2 から N
for i in range(N - length + 1):
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:
dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])

return dp[0][N - 1]

# --- 入力処理 ---
def main() -> None:
input_lines = sys.stdin.read().splitlines()
_: int = int(input_lines[0])
S: str = input_lines[1]

result: int = longest_palindromic_subsequence(S)
print(result)

if __name__ == "__main__":
main()

# ## ✅ 入力例

# 標準入力で以下のように与えると:

# ```
# 11
# programming
# ```

# 出力:

# ```
# 4
# ```

# ---

# ## ✅ 時間・空間計算量

# | 項目 | 値 | 説明 |
# | ----- | -------- | ------------------- |
# | 時間計算量 | `O(N^2)` | 二重ループ(最大1000×1000) |
# | 空間計算量 | `O(N^2)` | 二次元リスト(最大1000×1000) |

# * `N = 1000` の場合でも、2次元配列は約4MB(Pythonの整数で1要素8バイト想定) → **余裕で収まります(制限1024MiB)**
# * 実行時間は最大でも **100万ステップ程度**なので、CPython 3.11.4でも1秒以内で完了します。

# ---

# ## ✅ 補足:DPテーブルを1次元に圧縮することも可能?

# はい、理論上は可能ですが、今回のように `dp[i+1][j-1]` を参照する遷移がある場合は「全ての状態を保持」していないと正しく計算できないため、**2次元テーブルが妥当**です。
Loading