NoiminのNoise

競技プログラミング (多め) とWeb (たまに) ,自然言語処理 (ブログではまだ)。数式の書き方を一気に KaTeX に変えようとして記事を全削除してインポートし直すなどしたので,過去にブックマークされた記事は URL が変わってしまっている可能性があります…….

TopCoder SRM 752 Div 1 Easy - ReconstructNumber

問題文: TopCoder Statistics - Problem Statement

公式解説: SRM 752 Editorial - Topcoder

問題概要

ある n+1 ($ n \leq 2000 $) 桁以下の数値について,隣あった桁の数字の大小関係が "=!><" の文字を含む長さ n の文字列で与えられる.

このとき,「ある n+1 桁以下の数値」としてありうる最小の数を求めよ.leading zero(s) を含んではいけない.

解法概要

(何桁目まで決定したか, 最後に決定した桁の数) の組を状態として,上の桁から順に見ていく.最小の数を求めたいので小さい数字から順番に試していき,その数字にしてもその後 (それよりも下の桁で) 矛盾が出ないとなったら OK.

全ての状態について,もう計算した/してない を管理しておけば (ソースコード中の visited),状態の数は高々 2000 * 10 通りなので余裕で間に合う.

小さい順に試すだけではなく,「この状態はもう計算済み?」「計算済みだとしたら,この状態を使って条件を満たす数値は作れる?」この2つをフィードバックさせることが大事.

ソースコード

#include <string>
#include <iostream>

using namespace std;

bool visited[2001][10], ok[2001][10]; // true で初期化されているので注意
int next_digit[2001][10];

class ReconstructNumber {
    public:

    int n;
    string s;

    // i桁目をjと決めたとき,(i+1)桁めを決める
    bool solve(int i, int j) {
        if(i == n) return true;
        if(visited[i][j]) return ok[i][j];
        
        visited[i][j] = true;

        if(s[i] == '=') {
            if(solve(i+1, j)) {
                ok[i][j] = true;
                next_digit[i][j] = j;
                return true;
            }
        } else if(s[i] == '!') {
            for(int k=0;k<10;++k) {
                if(j == k) continue;
                if(solve(i+1, k)) {
                    ok[i][j] = true;
                    next_digit[i][j] = k;
                    return true;
                }
            }
        } else {
            int k_min = ((s[i] == '<')?j+1:0), k_max1 = ((s[i] == '<')?10:j);
            for(int k=k_min;k<k_max1;++k) {
                if(solve(i+1, k)) {
                    ok[i][j] = true;
                    next_digit[i][j] = k;
                    return true;
                }
            }
        }
        return false;
    }
    
    string smallest(string comparisons) {
        s = comparisons;
        n = s.length();
        fill(visited[0], visited[n+1]+10, false);
        fill(ok[0], ok[n+1]+10, false);
        
        for(int j=1;j<10;++j) {
            if(solve(0, j)) {
                string ans = "";
                int d = j;
                for(int i=0;i<n;++i) {
                    ans += char('0' + d);
                    d = next_digit[i][d];
                }
                ans += char('0' + d);
                return ans;
            }
        }
        return "";
    }
};

所感

tourist 氏に撃墜してもらった思い出の問題.

再帰で実装するとスマートに解けるタイプのDP,要精進.