mayoko’s diary

プロコンとかいろいろ。

AtCoder Beginner Contest 008 D - 金塊ゲーム

解法

まず 99 点の部分点解法を考えてみます。

一回金塊を取ると, 長方形は 4 つに分断されます。分断された各長方形からは, 別れた他の長方形の金塊を取りに行くことは出来ません。よって, 区間 dp 的に, dp[dlX][dlY][urX][urY] = (左下の頂点と右下の頂点を決めた時, その中で取れる金塊の最大値) とすれば, 問題が解けます。

満点解法では, この dp を上手く圧縮します。よく考えると, dlX, ... として考えられるのは, 各回収装置の座標を (xi, yi) として,

  • (xi, yi)
  • (xj, yi)
  • (0, yi), (xi, 0), (W, yi), (xi, H)

のみであるので, これらを座標圧縮(?)しておきましょう。これらの組み合わせとして考えられるのは, O(N^2) 程度で, dp[dl][ur] = (左下の頂点が dl, 右上の頂点が ur の場合の回収できる金塊の最大値) とすれば答えが得られます。

const int MAX = 2000;
ll dp[MAX][MAX];
pii pos[MAX];
int X[40], Y[40];
int N;

ll dfs(int dl, int ur, map<pii, int>& mp) {
    ll& ret = dp[dl][ur];
    if (ret >= 0) return ret;
    ret = 0;
    int x0 = pos[dl].first, y0 = pos[dl].second;
    int x1 = pos[ur].first, y1 = pos[ur].second;
    for (int i = 1; i < N-1; i++) {
        if (x0 < X[i] && X[i] < x1 && y0 < Y[i] && Y[i] < y1) {
            ll tmp = (x1-x0-1) + (y1-y0-1) - 1;
            // 左下
            tmp += dfs(dl, mp[pii(X[i], Y[i])], mp);
            // 左上
            tmp += dfs(mp[pii(x0, Y[i])], mp[pii(X[i], y1)], mp);
            // 右下
            tmp += dfs(mp[pii(X[i], y0)], mp[pii(x1, Y[i])], mp);
            // 右上
            tmp += dfs(mp[pii(X[i], Y[i])], ur, mp);
            ret = max(ret, tmp);
        }
    }
    return ret;
}

int main() {
    cin.tie(0);
    ios::sync_with_stdio(false);
    int W, H;
    cin >> W >> H;
    cin >> N;
    for (int i = 1; i <= N; i++) cin >> X[i] >> Y[i];
    X[0] = Y[0] = 0;
    X[N+1] = W+1, Y[N+1] = H+1;
    N += 2;
    map<pii, int> mp;
    for (int i = 0; i < N; i++) {
        mp[pii(X[i], Y[i])] = 0;
        for (int j = i+1; j < N; j++) {
            mp[pii(X[i], Y[j])] = 0;
            mp[pii(X[j], Y[i])] = 0;
        }
    }
    int k = 0;
    for (auto& p : mp) {
        p.second = k;
        pos[k] = p.first;
        k++;
    }
    memset(dp, -1, sizeof(dp));
    cout << dfs(mp[pii(0, 0)], mp[pii(W+1, H+1)], mp) << endl;
    return 0;
}