結果

問題 No.3408 1215 Segments
コンテスト
ユーザー kencho
提出日時 2025-12-09 22:32:04
言語 PyPy3
(7.3.15)
結果
AC  
実行時間 734 ms / 2,500 ms
コード長 8,697 bytes
記録
記録タグの例:
初AC ショートコード 純ショートコード 純主流ショートコード 最速実行時間
コンパイル時間 330 ms
コンパイル使用メモリ 82,352 KB
実行使用メモリ 78,168 KB
最終ジャッジ日時 2025-12-14 23:36:54
合計ジャッジ時間 11,823 ms
ジャッジサーバーID
(参考情報)
judge5 / judge3
このコードへのチャレンジ
(要ログイン)
ファイルパターン 結果
sample AC * 4
other AC * 46
権限があれば一括ダウンロードができます

ソースコード

diff #
raw source code

import sys

# 再帰呼び出しを排除したため setrecursionlimit は不要ですが、
# 念のためインポートは残します。

def main():
    # 高速I/O
    input_data = sys.stdin.read().split()
    if not input_data:
        return
    
    n_str = input_data[0]
    length_n = len(n_str)
    
    # ターゲットの各桁を数値化(アクセスのたびにint変換しない)
    target_digits = [int(c) for c in n_str]
    
    # DFS用のカウント配列
    counts = [0] * 10
    
    # 答えの格納用(リストにして参照書き換え可能にする)
    best_ans = [None]

    # -----------------------------------------------------
    # 非再帰版: 最小値構築ロジック
    # ターゲット文字列 target_str 以上で、構成 counts を満たす
    # 最小の文字列を返す。存在しない場合は None。
    # -----------------------------------------------------
    def find_smallest_iterative(current_counts, total_digits, target_str, t_digits):
        # -------------------------------------------------
        # ケース1: 桁数がターゲットより多い場合
        # -> 最小の非ゼロ数字を先頭にし、残りを昇順に並べる
        # -------------------------------------------------
        if total_digits > len(target_str):
            for d in range(1, 10):
                if current_counts[d] > 0:
                    current_counts[d] -= 1
                    # 文字列構築
                    res = [str(d)]
                    for k in range(10):
                        c = current_counts[k]
                        if c > 0:
                            res.append(str(k) * c)
                    current_counts[d] += 1 # backtrack (復元)
                    return "".join(res)
            return None

        # -------------------------------------------------
        # ケース2: 桁数が同じ場合
        # -> ターゲットとの共通接頭辞(Prefix)を利用して探索
        # -------------------------------------------------
        # 戦略:
        # 1. countsで target の先頭から最大何文字まで一致させられるか(max_p)を調べる。
        # 2. 一致長 P = max_p から 0 まで逆順にループする。
        #    分岐点 k (0 <= k <= P) において、target[k] より大きい数字 d を
        #    counts から選べるなら、それが「分岐点 k で成立する最小解」である。
        #    k を大きい方から試すことで、よりターゲットに近い(小さい)値が見つかる。

        # 作業用カウント配列
        temp_counts = list(current_counts)
        L = len(target_str)
        
        # 1. 最大一致長 max_p を計算
        max_p = 0
        for val in t_digits:
            if temp_counts[val] > 0:
                temp_counts[val] -= 1
                max_p += 1
            else:
                break
        
        # もし最後まで一致可能なら、それが最小値 (targetそのもの)
        if max_p == L:
            return target_str

        # 2. 分岐点を後ろから探す
        # temp_counts は現在 max_p 文字分減った状態
        # k は「分岐するインデックス」
        # k = max_p のときは、prefix一致の後、次の文字で勝負する(実際は不一致で止まった箇所)
        for k in range(max_p, -1, -1):
            # ループの初回以外は、一つ後ろの桁(target[k])を「使わなかった」ことにするので在庫に戻す
            if k < max_p:
                temp_counts[t_digits[k]] += 1
            
            # 分岐点 k において、target[k] より大きい最小の数字を探す
            current_digit_val = t_digits[k]
            found_d = -1
            
            # target[k] + 1 から 9 まで探索
            for d in range(current_digit_val + 1, 10):
                if temp_counts[d] > 0:
                    found_d = d
                    break
            
            if found_d != -1:
                # 見つかった場合、これより上位の桁での分岐より必ず小さくなるので確定
                temp_counts[found_d] -= 1
                
                # 結果構築: target[:k] + found_d + 残りを昇順
                res = []
                # 接頭辞
                res.append(target_str[:k])
                # 分岐文字
                res.append(str(found_d))
                # 残り(昇順)
                for digit in range(10):
                    c = temp_counts[digit]
                    if c > 0:
                        res.append(str(digit) * c)
                
                return "".join(res)
        
        return None

    # -----------------------------------------------------
    # 整合性チェック関数 (変更なし、計算のみ)
    # -----------------------------------------------------
    def check_and_update(cnt, L):
        c0, c1, c2, c3, c4, c5, c6, c7, c8, c9 = cnt

        if (L + c0 + c6 + c8) & 1: return

        base = (L + c0 + c6 + c8) >> 1
        s4 = c0 + c2 + c6 + c8
        
        one = base - s4
        if one < 0 or one > c1: return

        s3 = c0 + c2 + c3 + c5 + c6 + c8 + c9
        nine = s3 - base
        if nine < 0 or nine > c9: return

        s1 = c0 + c1 + c2 + c3 + c4 + c7 + c8 + c9
        zero = s1 - one - base
        if zero < 0 or zero > c0: return

        s5 = c0 + c4 + c5 + c6 + c8 + c9
        seven = base - (s5 - zero + one)
        if seven < 0 or seven > c7: return

        s0 = c0 + c2 + c3 + c5 + c6 + c7 + c8 + c9
        six = s0 - zero - base
        if six < 0 or six > c6: return

        s6 = c2 + c3 + c4 + c5 + c6 + c8 + c9
        if s6 + zero != base: return

        # 有効なら最小値を構築
        candidate = find_smallest_iterative(cnt, L, n_str, target_digits)
        
        if candidate is not None:
            if best_ans[0] is None:
                best_ans[0] = candidate
            else:
                # 文字列長比較 -> 辞書順比較
                len_cand = len(candidate)
                len_best = len(best_ans[0])
                if len_cand < len_best:
                    best_ans[0] = candidate
                elif len_cand == len_best and candidate < best_ans[0]:
                    best_ans[0] = candidate

    # -----------------------------------------------------
    # 非再帰 DFS: スタックを使用
    # -----------------------------------------------------
    def run_iterative_dfs(total_len):
        # スタックの要素: [digit_index, current_val, remaining_sum]
        # digit_index: 現在決めている数字 (0-8)
        # current_val: counts[digit_index] として試している値 (-1は未処理を示す)
        # remaining_sum: 残りの割り当て可能数
        
        # 初期状態: 数字0について、まだ値は決めていない(-1)、残り全て(total_len)
        stack = [[0, -1, total_len]]
        
        while stack:
            frame = stack[-1]
            d = frame[0]
            
            # 直前のループ処理のクリーンアップ(バックトラック)
            if frame[1] != -1:
                counts[d] = 0 # 戻す
            
            # 次の値を試す
            frame[1] += 1
            val = frame[1]
            rem = frame[2]
            
            # ループ終了条件: 割り当て数が残り数を超えたら pop
            if val > rem:
                stack.pop()
                continue
            
            # 値をセット
            counts[d] = val
            
            if d == 8:
                # 数字8まで決めたら、数字9は残り全てで確定
                # ここで再帰の底の処理を行う
                last_rem = rem - val
                counts[9] = last_rem
                check_and_update(counts, total_len)
                counts[9] = 0
                # 8のループを継続するため、ここではpopせず、次のループへ
                continue
            else:
                # 次の深さ(d+1)へ
                # 新しいフレームをプッシュ
                stack.append([d + 1, -1, rem - val])

    # -----------------------------------------------------
    # メインループ
    # -----------------------------------------------------
    # 長さ L を Nの長さ から 20 まで探索
    for l in range(length_n, 21):
        best_ans[0] = None
        run_iterative_dfs(l)
        
        if best_ans[0] is not None:
            print(best_ans[0])
            break

if __name__ == '__main__':
    main()
0