🚀 ダブリング法による数字操作問題
詳細アルゴリズム解析

📋 問題概要

操作定義: 数値から各桁の数字の和を引く
目標: 1からNの各数値に対してK回操作後の値を求める
制約: N ≤ 300,000, K ≤ 10⁹
108
108-(1+0+8)=99
99-(9+9)=81
81-(8+1)=72

⚖️ アルゴリズム選択の比較

アプローチ 時間計算量 空間計算量 制約での実行可能性
単純な繰り返し O(N × K) O(1) ❌ TLE (最大 3×10¹⁴ 操作)
サイクル検出 O(N × √max_value) O(√max_value) ⚠️ 大きな値でTLE
ダブリング法 O(N × log K) O(N × log K) ✅ 高速 (最大 9×10⁶ 操作)

🔄 ダブリング法の原理

基本概念

1

事前計算

各値から2ⁿ回操作後の値を全て計算

2

二進分解

K を2の冪の和で表現
例: 13 = 8+4+1 = 2³+2²+2⁰

3

高速計算

事前計算した値を組み合わせて
O(log K)で結果を取得

ダブリングテーブル構築例

値\ビット 2⁰=1回 2¹=2回 2²=4回 2³=8回
10 9 0 0 0
20 18 9 0 0
108 99 81 45 36
123 117 102 99 81

🔧 実装の詳細解析

1. 基本操作関数

function getDigitSum(n: number): number { let sum = 0; while (n > 0) { sum += n % 10; // 最下位桁を取得 n = Math.floor(n / 10); // 桁を右シフト } return sum; } // 例: getDigitSum(123) → 1+2+3 = 6 // 計算量: O(log₁₀ n) ≈ 桁数に比例

2. ダブリングテーブル構築

構築過程:
1. table[0][i] = i から1回操作後の値を計算
2. table[bit][i] = table[bit-1][table[bit-1][i]]
3. 2^bit回 = 2^(bit-1)回 + 2^(bit-1)回 の性質を利用
// bit=1の構築例(2回操作) for (let i = 0; i <= maxIndex; i++) { table[1][i] = table[0][table[0][i]]; // i → 1回操作 → 1回操作 = 2回操作 } // bit=2の構築例(4回操作) for (let i = 0; i <= maxIndex; i++) { table[2][i] = table[1][table[1][i]]; // i → 2回操作 → 2回操作 = 4回操作 }

3. クエリ処理(K回操作の計算)

K=13をビット分解
13 = 1101₂
8+4+1回操作
// K=13 (1101₂) の場合 function queryExample(start: number) { let current = start; // ビット0が立っている → 1回操作 if (13 & 1) current = table[0][current]; // ビット2が立っている → 4回操作 if (13 & 4) current = table[2][current]; // ビット3が立っている → 8回操作 if (13 & 8) current = table[3][current]; return current; // 合計13回操作後の値 }

💾 メモリ使用量解析

ダブリングテーブル
サイズ: maxIndex × log₂K
例: 600,000 × 30 = 18M 要素
メモリ: 約72MB
結果配列
サイズ: N 個の文字列
例: 300,000 個
メモリ: 約2-3MB
一時変数
クエリ処理用: O(1)
その他: 最小限
メモリ: < 1MB
メモリ最適化技術:
• 適応的上限設定: Math.min(N×2, 1,000,000)
• 範囲外は直接計算: 事前計算範囲を制限
• 定期的GC: 大きなNでのメモリクリーンアップ

⚡ パフォーマンス比較

実行時間比較 (N=300,000, K=10⁹)

単純繰り返し
タイムアウト
サイクル検出
~10秒
ダブリング法
~0.8秒
段階 処理内容 計算量 実行時間(推定)
前処理 ダブリングテーブル構築 O(maxIndex × log K) ~200ms
クエリ処理 N個の値を並列計算 O(N × log K) ~500ms
出力生成 文字列結合・出力 O(N) ~100ms

🎯 ハイブリッド最適化戦略

1

閾値判定

K ≤ 100: 直接計算
K > 100: ダブリング法

2

範囲チェック

事前計算範囲内: テーブル使用
範囲外: 直接計算フォールバック

3

メモリ管理

適応的上限設定
定期的ガベージコレクション

最適化の効果:
• 小さなK: オーバーヘッド削減で2-3倍高速化
• 大きなK: ダブリング法で指数的高速化
• メモリ効率: 制約内で最大パフォーマンスを実現

🎮 インタラクティブデモ

アルゴリズム動作確認

🎯 結論

ダブリング法の優位性:
時間効率: O(N log K) - 制約下で確実に実行可能
空間効率: 適応的制限でメモリ使用量を最適化
実装安定性: TypeScriptの型安全性で堅牢な実装
拡張性: より大きな制約にも対応可能な設計