Typical DP Contest D - サイコロ

解法

サイコロの出た目の積の素因数には 2, 3, 5 しかない.
つまり,D にそれ以外の素因数があればダメ.
そうでなければ,D = 2^a * 3^b * 5^c と一意的に表すことができる.
dp[x][y][z] := 今現在,2の素因数が x 個,3 の素因数が y 個,5 の素因数が z 個となる確率
というDPテーブルを考えれば,そこからもう一つサイコロを投げた時の遷移が簡単に書ける.
計算量は O(Nabc) となる.
N <= 100 だが,a, b, c はもっと小さいので余裕で間に合う.(2 ^ 64 で 10^18 を超えてしまう)

ソースコード

#include <bits/stdc++.h>
using namespace std;

using ll = long long;

template<typename T>
std::vector<T> table(int n, T v) { return std::vector<T>(n, v); }

template <class... Args>
auto table(int n, Args... args) {
    auto val = table(args...);
    return std::vector<decltype(val)>(n, std::move(val));
}

constexpr int dx[6] = {0, 1, 0, 2, 0, 1};
constexpr int dy[6] = {0, 0, 1, 0, 0, 1};
constexpr int dz[6] = {0, 0, 0, 0, 1, 0};

int main() {
    cout << fixed << setprecision(10);

    ll n, d; cin >> n >> d;

    vector<int> cnt;
    for(auto const v : {2, 3, 5}) {
        int c = 0;
        while(d % v == 0) {
            d /= v;
            ++c;
        }
        cnt.push_back(c);
    }
    if(d != 1) {
        cout << 0 << endl;
        return 0;
    }

    auto dp = table(cnt[0] + 1, cnt[1] + 1, cnt[2] + 1, 0.0);
    dp[0][0][0] = 1;
    for(int lp = 0; lp < n; ++lp) {
        auto nxt = table(cnt[0] + 1, cnt[1] + 1, cnt[2] + 1, 0.0);
        for(int x = 0; x <= cnt[0]; ++x) {
            for(int y = 0; y <= cnt[1]; ++y) {
                for(int z = 0; z <= cnt[2]; ++z) {
                    for(int i = 0; i < 6; ++i) {
                        const int nx = min(cnt[0], x + dx[i]);
                        const int ny = min(cnt[1], y + dy[i]);
                        const int nz = min(cnt[2], z + dz[i]);
                        nxt[nx][ny][nz] += dp[x][y][z] / 6;
                    }
                }
            }
        }
        dp = move(nxt);
    }

    cout << dp[cnt[0]][cnt[1]][cnt[2]] << endl;
}

Typical DP Contest C - トーナメント

解法

まず各山について考える.たとえば,8人いるなら [0, 7], [0, 3], [4, 7], [0, 1], … などが各山にあたる.
ある山 X に属する個人が,X の中で優勝する確率と,次に X が対戦することになる山 Y の同様の確率が求まるとする.
すると,山 X と Y を統合した山 Z について,Z の中で X および Y に属する個人が勝利する確率を求めることができる.
これをループあるいは再帰で実装すればよい.

計算量は O(K * 2^(2K)).

ソースコード

#include <bits/stdc++.h>
using namespace std;

double calc_prob(double r1, double r2) {
    return 1.0 / (1 + pow(10, (r2 - r1) / 400));
}

int main() {
    cout << fixed << setprecision(10);

    int k; cin >> k;
    vector<int> R(1 << k);
    for(auto& x : R) cin >> x;

    function<vector<double>(int, int)> solve = [&] (int l, int r) {
        if(r - l == 1) {
            return vector<double>{1.0};
        }
        vector<double> res(r - l);
        const int sz = (r - l) / 2;
        auto l_res = solve(l, l + sz), r_res = solve(l + sz, r);
        for(int i = 0; i < sz; ++i) {
            for(int j = 0; j < sz; ++j) {
                res[i] += calc_prob(R[l + i], R[l + j + sz]) * l_res[i] * r_res[j];
                res[j + sz] += calc_prob(R[l + j + sz], R[l + i]) * l_res[i] * r_res[j];
            }
        }
        return res;
    };

    for(auto&& ans : solve(0, 1 << k)) {
        cout << ans << endl;
    }
}

Typical DP Contest B - ゲーム

解法

漸化式的にも解けるが,メモ化再帰で書くほうがわかりやすいかもしれない.
この場合,minimax法でやる.
dp[l][r] := 左の一番上が l 枚目,右の一番上が r 番目の状態から始めた時の,(その瞬間の)先手の得点 - 後手の得点の最大値
としてメモ化再帰する.
答えは sum を全カードの総和として (sum + dp[0][0]) / 2 で求まる.

ソースコード

#include <bits/stdc++.h>
using namespace std;

constexpr int inf = 1e9;

int main() {
    int A, B; cin >> A >> B;
    vector<int> a(A), b(B);
    for(auto& x : a) cin >> x;
    for(auto& x : b) cin >> x;

    const int sum = accumulate(a.begin(), a.end(), 0) + accumulate(b.begin(), b.end(), 0);

    vector<vector<int>> dp(A + 1, vector<int>(B + 1, -inf));
    function<int(int, int)> rec = [&] (int l, int r) {
        if(dp[l][r] != -inf) return dp[l][r];
        if(l == A && r == B) return dp[l][r] = 0;

        int& res = dp[l][r];
        if(l != A) res = a[l] - rec(l + 1, r);
        if(r != B) res = max(res, b[r] - rec(l, r + 1));
        return res;
    };

    cout << (sum + rec(0, 0)) / 2 << endl;
}

Typical DP Contest A - コンテスト

解法

基本的なナップサック問題
p(i), N <= 100 なので,高々 10^6 のループで終わる.

ソースコード

#include <bits/stdc++.h>
using namespace std;

constexpr int max_p = 10000;

int main() {
    int n; cin >> n;
    vector<int> p(n);
    for(auto& x : p) cin >> x;

    vector<bool> dp(max_p + 1);
    dp[0] = true;
    for(int i = 0; i < n; ++i) {
        for(int j = max_p - p[i]; j >= 0; --j) {
            dp[j + p[i]] = dp[j + p[i]] | dp[j];
        }
    }

    cout << count(dp.begin(), dp.end(), true) << endl;
}

Typical DP Contest まとめ

Typical DP Contest - Typical DP Contest | AtCoder

Typical DP Contest の全ての問題の解法を書いていきたい.

Typical DP Contest A - コンテスト - すいバカ日誌
Typical DP Contest B - ゲーム - すいバカ日誌
Typical DP Contest C - トーナメント - すいバカ日誌
Typical DP Contest D - サイコロ - すいバカ日誌
Typical DP Contest E - 数 - すいバカ日誌
Typical DP Contest F - 準急 - すいバカ日誌
Typical DP Contest G - 辞書順 - すいバカ日誌
Typical DP Contest H - ナップザック - すいバカ日誌
Typical DP Contest I - イウィ - すいバカ日誌
Typical DP Contest J - ボール - すいバカ日誌
Typical DP Contest K - ターゲット - すいバカ日誌
Typical DP Contest L - 猫 - すいバカ日誌
Typical DP Contest M - 家 - すいバカ日誌
Typical DP Contest N - 木 - すいバカ日誌
Typical DP Contest O - 文字列 - すいバカ日誌
Typical DP Contest P - うなぎ - すいバカ日誌
Typical DP Contest Q - 連結 - すいバカ日誌
Typical DP Contest R - グラフ - すいバカ日誌
Typical DP Contest S - マス目 - すいバカ日誌
Typical DP Contest T - フィボナッチ - すいバカ日誌

・2017/08/27
無事全部解き切りました.一部の問題は自力で解けなかったけど.

AOJ 1359 Wall Clocks

解法

各点から半直線が2本出て,長方形の周上の2点で交わるが,それをそのまま処理すると面倒.
なので,(0, 0) -> (0, d) -> (w, d) -> (w, 0) -> (0, 0) という4辺を展開して,[0, 2(w+d)] という数直線上の区間にしてしまうと楽になる.
半直線は 45度 なので,展開後の座標は簡単な計算により求まる.
各点 i から見える範囲が [li, ri] とすると,ri の 昇順にソートしてやるとよい.最初にどこに時計を置くかを決めれば,あとは時計回り(つまり数直線の右方向)に,置いた時計が見えなくなるまで飛ばして,見えなくなったら追加することで解が得られる.
あとは,最初に時計を置く位置を全探索すれば最適解が求まる.

ソースコード

#include <bits/stdc++.h>
using namespace std;

using pii = pair<int, int>;

int main() {
    int n, w, d;
    cin >> n >> w >> d;
    vector<pii> v;
    for(int i=0; i<n; ++i) {
        int x, y;
        char f;
        cin >> x >> y >> f;
        if(f == 'W') {
            v.emplace_back(x + y, -x + y);
        } else if(f == 'N') {
            v.emplace_back(2*d + x - y, x + y);
        } else if(f == 'E') {
            v.emplace_back(2*(d+w) - x - y, 2*d + x - y);
        } else {
            v.emplace_back(2*(d+w) - x + y ,2*(d+w) - x - y);
        }
    }
    sort(v.begin(), v.end());
    int L = 2*(w + d);
    int res = 1e9;
    for(int i=0; i<n; ++i) {
        auto tmp = v;
        for(int j=i-1; j>=0; --j) {
            if(tmp[j].first < tmp[i].first) {
                tmp[j].first += L;
                tmp[j].second += L;
            }
        }
        sort(tmp.begin(), tmp.end());
        int cnt = 1;
        int now = tmp[0].first;
        for(int j=0; j<n; ++j) {
            if(tmp[j].second > now) {
                now = tmp[j].first;
                cnt++;
            }
        }
        res = min(res, cnt);
    }
    cout << res << endl;
}

AOJ 1348 Space Golf

解法

完全にやるだけ.多少無駄なことをしても入力サイズが小さいので余裕で通る.
ご丁寧に式が書かれているので,それに従って候補を全部列挙し,それらが条件をみたすか(つまり接触しないか)を判定する.条件をみたす中で最小の値が答え.

明らかに最適なのは vx = vy となるときで,これでうまくいかないならどれかに接触してしまうということ.その時は,接触するギリギリを全て試してみて,その中で条件を満たし,かつ最小の値を求める.

g = 9.8 じゃないので注意.

ソースコード

#include <bits/stdc++.h>
using namespace std;

constexpr double INF = 1e9;
constexpr double eps = 1e-8;

int main() {
    double d;
    int n, b;
    cin >> d >> n >> b;
    vector<double> p(n), h(n);
    for(int i=0; i<n; ++i) {
        cin >> p[i] >> h[i];
    }
    double res = INF;
    for(int i=0; i<=b; ++i) {
        double interval = d / (i+1);
        bool neg = false;
        for(int j=0; j<=i; ++j) {
            for(int i=0; i<n; ++i) {
                neg |= abs(interval * j - p[i]) < eps;
            }
        }
        if(neg) {
            continue;
        }
        vector<double> vx, vy;
        vx.push_back(sqrt(interval / 2));
        vy.push_back(vx.back());
        for(int j=0; j<=i; ++j) {
            double now = interval * j;
            for(int k=0; k<n; ++k) {
                double pp = p[k] - now;
                if(0 < pp && pp < interval) {
                    vx.push_back(sqrt(1 / (2*h[k]) * pp * (interval - pp)));
                    vy.push_back(interval / (2 * vx.back()));
                }
            }
        }
        for(int j=0; j<vx.size(); ++j) {
            bool ok = true;
            for(int k=0; k<=i; ++k) {
                double now = interval * k;
                for(int l=0; l<n; ++l) {
                    double pp = p[l] - now;
                    if(0 < pp && pp < interval) {
                        double t = pp / vx[j];
                        double hh = vy[j] * t - 0.5 * t*t;
                        ok &= (hh - h[l]) >= -eps;
                    }
                }
            }
            if(ok) {
                res = min(res, hypot(vx[j], vy[j]));
            }
        }
    }
    cout << fixed << setprecision(10) << res << endl;
}