hogecoder

tsutaj 競技プログラミングの記録

TopCoder SRM 730 Div1 Easy (Div2 Hard): StonesOnATree

するめうめ

tsutaj.hatenablog.com

問題概要

原文 → TopCoder Statistics - Problem Statement

頂点に重み  w_i がついている、  N 頂点からなる木がある。子よりも親の方がインデックスが小さく、重みについても子と同じか小さいことが保証される。

この木に石を置いたり、取り除いたりすることを考える。ある頂点に石が置ける条件とは、その頂点の子すべてに石が置かれていることである。取り除く際は、どこを取り除いてもよい。

ある状態に対して、石が置かれている頂点に対応する重みの和をコストとするとき、頂点  0 に石を置くために必要な最大コストの最小値を求めよ。

解説

ドワコン 2018 予選の D 問題 と設定と似ていますが、「重みが単調増加である」という点が異なります。

 p_i かつ  w \left[ i-1 \right] \leq w \left[ i \right] であることから、任意の頂点  v の重みについて、その子の重みと等しいか小さくなります。この制約により、ある頂点  v に対して、「その子  c_1 に石が置けるように操作し、それが終わってから別の子  c_2 に石が置けるように操作する」ことが最適になります。 c_1 に対して動かしている途中に  c_2 に対して動かす必要がないということです。

よって、 \mathrm{dp} \left[ i \right] := 頂点  i に石を置くために必要な最大コストの最小値 とし、これを葉から根の方向に更新すれば良いです。

Div1 Easy では子の数は高々  2 なので、場合分けします。

子の数が  0 である場合は、葉なので明らかに  w_i が入ります。

 1 である場合は、自分の重みとその子の重みの和と、子に石を置くために必要なコストの  \max です。

 2 である場合について考えます。説明のため子である頂点を  u, v とします。このとき、子に石を置く順番は  u \rightarrow v または  v \rightarrow u のどちらかです。このうち、コストが小さくなる方を選択すれば良いです。

Div2 Hard では二分木とは限らないので、もう少し考えます。

頂点  v の子を  p_1, p_2, \dots, p_k とします。とりあえず、 p_1, p_2, \dots, p_k の順番に石を置き、それから  v に石を置くことにしましょう。

 p_1 を置くまでのコストは、 DP で計算している値そのものなので、 \mathrm{dp} \left[ p_1 \right] です。

 p_2 を置くまでのコストは、既に  p_1 が置かれているため  \mathrm{w} \left[ p_1 \right] + \mathrm{dp} \left[ p_2 \right] になります。

このように、石を置くのが  k 番目となる子  p_k について、そのコストは  \mathrm{w} \left[ p_1 \right] + \dots + \mathrm{w} \left[ p_{k-1} \right] + \mathrm{dp} \left[ p_k \right] となり、あとは適切に石を置く順番を決めることでコストを最小化しようということになります。

ここで、任意の  v について、 \mathrm{w} \left[ v \right] \leq \mathrm{dp} \left[ v \right] であることを利用します (これは  v を置くために必ず  \mathrm{w} \left[ v \right] かかることを考慮すれば明らかです)。  \mathrm{w} \left[ p_1 \right] + \dots + \mathrm{w} \left[ p_{k-1} \right] \leq \mathrm{w} \left[ p_1 \right] + \dots + \mathrm{dp} \left[ p_{k-1} \right] ですから、  \mathrm{dp} の値と  \mathrm{w} の値の差分が大きい物から先に配置していけば、最終的なコストは小さくなります。よって、このルールに従って子をソートすれば解けます。

最後に、子の重みの総和と自分自身の重みの和を考慮し忘れないように気をつけましょう。

ソースコード

Div1

class StonesOnATree {
    public:
    int minStones(vector<int> p, vector<int> w) {
        int N = w.size();
        vector< vector<int> > children(N);
        for(int i=0; i<N-1; i++) {
            children[ p[i] ].push_back(i+1);
        }

        vector<int> dp(N);
        for(int i=N-1; i>=0; i--) {
            int sz = children[i].size();
            if(sz == 0) {
                dp[i] = w[i];
            }
            if(sz == 1) {
                int u = children[i][0];
                dp[i] = max(dp[u], w[i] + w[u]);
            }
            if(sz == 2) {
                int u = children[i][0], v = children[i][1];
                // u -> v
                int vl = max(dp[u], w[u] + dp[v]);
                // v -> u
                int vr = max(dp[v], w[v] + dp[u]);
                dp[i] = max(w[i] + w[u] + w[v], min(vl, vr));
            }
        }
        return dp[0];
    }
};

Div2

class StonesOnATreeDiv2 {
    public:
    int minStones(vector<int> p, vector<int> w) {
        int N = w.size();
        vector< vector<int> > children(N);
        for(int i=0; i<N-1; i++) {
            children[ p[i] ].push_back(i+1);
        }

        vector<int> dp(N);
        for(int i=N-1; i>=0; i--) {
            sort(children[i].begin(), children[i].end(), [&](int l, int r) {
                return dp[l] - w[l] > dp[r] - w[r];
            });

            int sum_weight = 0;
            for(size_t k=0; k<children[i].size(); k++) {
                int u = children[i][k];
                dp[i] = max(dp[i], sum_weight + dp[u]);
                sum_weight += w[u];
            }
            sum_weight += w[i];
            dp[i] = max(dp[i], sum_weight);
        }
        return dp[0];
    }
};