AOJ 1184. 一般化ポーカー / Generic Poker

,

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=1184

解き終わるとやるだけだった気がしてくる。

solution

マッチするものの数と可能な手札の数をそれぞれ求める。 分母側は自明な$O(NML)$ DPで求まり、overflowすることもない。 分子側は面倒。全てのマッチ対象を($0$の位置を無視しての同値関係で割った上で)列挙するので、ざっくりと$O({}_{\min \{ m, 2l - 1 \}}C_{l})$とかだと思う。

分子側について。 例えば手札は$h = (0, 1, 1, 4, 5, 5, 5)$などと関数$h : L \to M$の形で表現することもできるが、そうではなく関数$h’ : M \to \mathbb{N}$の形で$h’ = (1, 2, 0, 0, 1, 3, 0, 0, 0, 0, 0)$のように表すことを考える。 手札の枚数$L \le 7$に対しランクの数$M \le 60$と大きく、そう書いたときはほとんどが$0$である。 パターン指示子a a+ a++ $\dots$は間を飛ばすことなく出現するという制約がある。 このため$h’ = (1, 2, 0, 0, 1, 3, 0, 0, 0, 0, 0)$がマッチするなら$(1, 2, 0, 0, 0, 0, 1, 3, 0, 0, 0)$や$(0, 0, 0, 0, 1, 2, 0, 0, 0, 1, 3)$もマッチ対象。 このように$0$はかなり自由に動かせるので、極力後ろに詰めた形を代表元としてそれだけ列挙するようにする。 例えば$h’$に対しては、末尾の$0$は明らかなので省略して$(1, 2, 0, 1, 3)$がそれ。

$0$で区切られた部分を「ブロック」と呼ぶことにする。 可能なブロックを全て列挙し、それが満たすことのできる変数の集合($a, b, c$の部分集合なので$8$通りであり、複数の可能性がある)も求めておく。 そしてこのブロックを(間に$0$を挟んで)付け加える操作をひとつの遷移とするDPを行ない、マッチする手札の同値類を全て列挙する。 あとはそのまま要素数を計算して足し合わせればよい。

implementation

#include <algorithm>
#include <cassert>
#include <cmath>
#include <iostream>
#include <numeric>
#include <set>
#include <vector>
#define repeat(i, n) for (int i = 0; (i) < int(n); ++(i))
#define repeat_from(i, m, n) for (int i = (m); (i) < int(n); ++(i))
#define whole(f, x, ...) ([&](decltype((x)) whole) { return (f)(begin(whole), end(whole), ## __VA_ARGS__); })(x)
using ll = long long;
using namespace std;
template <typename X, typename T> auto vectors(X x, T a) { return vector<T>(x, a); }
template <typename X, typename Y, typename Z, typename... Zs> auto vectors(X x, Y y, Z z, Zs... zs) { auto cont = vectors(y, z, zs...); return vector<decltype(cont)>(x, cont); }

vector<vector<ll> > calc_choose(int n) { // O(n^2)
    vector<vector<ll> > dp(n + 1);
    dp[0].assign(1, 1);
    repeat (i, n) {
        dp[i + 1].resize(i + 2);
        repeat (j, i + 2) {
            if (j - 1 >= 0) dp[i + 1][j] += dp[i][j - 1];
            if (j != i + 1) dp[i + 1][j] += dp[i][j];
        }
    }
    return dp;
}
constexpr int MAX_M = 60;
auto choose = calc_choose(MAX_M + 1);

vector<int> concat_hands(vector<int> const & xs, vector<int> const & ys) {
    if (xs.empty()) {
        return ys;
    } else {
        assert (not ys.empty());
        vector<int> zs = xs;
        zs.push_back(0);
        for (int y : ys) zs.push_back(y);
        return zs;
    }
}
ll calc_numerator(int n, int m, int l, vector<int> const & a, vector<int> const & b, vector<int> const & c, int star) {
    assert (m <= MAX_M);
    auto f = vectors(8, set<vector<int> >()); // f : used -> sets of lists of hands, where hand : a list of counts. e.g. (1, 1, 2, 2, 2, 3) is (2, 3, 1)
    repeat (star_count, star + 1) {
        repeat (star_set, pow(l, star_count)) {
            vector<int> stars;
            for (int s = star_set; stars.size() < star_count; ) {
                stars.push_back(s % l);
                s /= l;
            }
            if (not whole(is_sorted, stars)) continue;
            repeat_from (ofs_a, -1, l - a.size() + 1) {
                if (a.empty() and ofs_a != -1) break;
                repeat_from (ofs_b, -1, l - b.size() + 1) {
                    if (b.empty() and ofs_b != -1) break;
                    repeat_from (ofs_c, -1, l - c.size() + 1) {
                        if (c.empty() and ofs_c != -1) break;
                        vector<int> used(l);
                        if (ofs_a != -1) repeat (i, a.size()) used[ofs_a + i] += a[i];
                        if (ofs_b != -1) repeat (i, b.size()) used[ofs_b + i] += b[i];
                        if (ofs_c != -1) repeat (i, c.size()) used[ofs_c + i] += c[i];
                        for (int i : stars) used[i] += 1;
                        repeat (i, l) {
                            if (used[i] > n) {
                                used.clear();
                                break;
                            }
                            if (i + 1 < l and not used[i] and used[i + 1]) {
                                used.clear();
                                break;
                            }
                        }
                        while (not used.empty() and used.back() == 0) used.pop_back();
                        if (used.empty()) continue;
                        f[((ofs_a != -1) << 2) | ((ofs_b != -1) << 1) | (ofs_c != -1)].insert(used);
                    }
                }
            }
        }
    }
    int dp_size = min(m, 2 * l - 1);
    auto dp = vectors(dp_size + 1, 8, set<vector<int> >()); // dp : length * used -> hands
    dp[0][(a.empty() << 2) | (b.empty() << 1) | c.empty()].insert(vector<int>());
    set<vector<int> > completed;
    repeat (i, dp_size) repeat (j, 8) for (auto xs : dp[i][j]) {
        repeat (dj, 8) if (not (j & dj)) for (auto ys : f[dj]) {
            int di = ys.size();
            if (not (i + bool(i) + di <= dp_size)) continue;
            vector<int> zs = concat_hands(xs, ys);
            int acc = whole(accumulate, zs, 0);
            if (acc == l) {
                if ((j | dj) == 7) {
                    completed.insert(zs);
                }
            } else if (acc < l) {
                dp[i + bool(i) + di][j | dj].insert(zs);
            }
        }
    }
    ll result = 0;
    for (auto xs : completed) {
        assert (not xs.empty());
        int b = whole(count, xs, 0) + 1;
        int a = m - xs.size() + b;
        ll acc = choose[a][b];
        for (int x : xs) {
            acc *= choose[n][x];
        }
        result += acc;
    }
    return result;
}

ll calc_denominator(int n, int m, int l, vector<int> const & a, vector<int> const & b, vector<int> const & c, int star) {
    auto dp = vectors(m + 1, l + 1, ll());
    dp[0][0] = 1;
    repeat (i, m) {
        repeat (j, l + 1) {
            repeat (d, n + 1) if (j + d <= l) {
                dp[i + 1][j + d] += dp[i][j] * choose[n][d];
            }
        }
    }
    return dp[m][l];
}

int main() {
    while (true) {
        // input
        int n, m, l; cin >> n >> m >> l;
        if (n == 0 and m == 0 and l == 0) break;
        vector<int> a(l), b(l), c(l);
        int star = 0;
        repeat (i, l) {
            string s; cin >> s;
            int rank = s.length() - 1;
            int & it =
                s[0] == 'a' ? a[rank] :
                s[0] == 'b' ? b[rank] :
                s[0] == 'c' ? c[rank] :
                star;
            it += 1;
        }
        while (not a.empty() and a.back() == 0) a.pop_back();
        while (not b.empty() and b.back() == 0) b.pop_back();
        while (not c.empty() and c.back() == 0) c.pop_back();
        // solve
        ll num = calc_numerator(n, m, l, a, b, c, star);
        ll den = calc_denominator(n, m, l, a, b, c, star);
        // output
        printf("%.10Lf\n", num /(long double) den);
    }
    return 0;
}