Official

F - Fighter Takahashi Editorial by en_translator


The following fact holds.

Whenever you face a defeatable enemy, immediately defeat it.

Here, a defeatable enemy refers to an enemy on a vertex, reachable only via visited vertices, and whose \(s _ i\) is no more than the strength of \(s _ i\).

This is true because if there is a way to defeat all the enemies, where an enemy is not immediately defeated, then one can bring it forward so that the enemy is immediately defeated.

Therefore, if one can defeat all the enemies, then all the enemies can be defeated as follows:

  • If there is a defeatable enemy, defeat it.
  • Otherwise, take an appropriate medicine.

Consider the following DP (Dynamic Programming).

  • \(\operatorname{dp}[S]\coloneqq\) the maximum Takahashi’s strength when he took the medicine in the set \(S\)

By the observation above, it is sufficient to consider the following case: from the state of \(\operatorname{dp}[S\setminus\lbrace i\rbrace]\), take medicine \(i\), and defeat as many enemies as possible.

With an appropriate \(O(N\log N)\) precalculation, a simulation step costs \(O(N)\) time, and the transition occurs \(O(2^PP)\) times, where \(P\) is the number of medicine, for a total complexity of \(O(N(2^PP+\log N))\).

A sample code follows.

#include <iostream>
#include <vector>
#include <ranges>
#include <numeric>
#include <algorithm>
#include <utility>

int main() {
    using namespace std;

    constexpr unsigned long max_strength{1000000000};

    unsigned N;
    cin >> N;

    struct vertex_info {
        unsigned potion_mask;        // set of medicine required to erase this vertex
        unsigned parent;             // parent of this vertex
        unsigned long need_strength; // minimum strength required to erase this vertex
        unsigned long gain;          // strength gained by erasing this vertex (or 0 if there is a medicine on this vertex)
    };
    struct potion_info {
        unsigned index;         // index of vertex
        unsigned long multiply; // ratio of strength when the medicine is taken
    };
    enum vertex_type {
        ENEMY = 1,
        POTION
    };

    vector<vertex_info> vertex(N - 1);
    vector<potion_info> potion;
    vertex.emplace(begin(vertex), 0, 0, 0, 1); // root
    for (unsigned type, i{1}; auto &&[m, p, strength, gain] : vertex | views::drop(1)) {
        cin >> p >> type >> strength >> gain;
        m = vertex[p].potion_mask;         // The parent must be erased before the vertex itself is erased
        if (type == vertex_type::POTION) { // If it is a medicine
            m |= 1U << size(potion);       // This medicine also has to be taken
            potion.emplace_back(i, exchange(gain, 0));
        }
        if (vertex[--p].gain) // If the parent is enemy
            strength = clamp(vertex[p].need_strength + vertex[p].gain, strength, max_strength);
        else // If the parent is medicine
            strength = clamp(vertex[p].need_strength * potion[bit_width(vertex[p].potion_mask) - 1].multiply, strength, max_strength);
        ++i;
    }

    // Sort the vertex in ascending order of the strength required to erase it
    {
        vector<unsigned> sorted_index(N), sorted_index_inv(N);
        iota(begin(sorted_index), end(sorted_index), 0U);
        ranges::stable_sort(sorted_index, {}, [&vertex](auto i) { return vertex[i].need_strength; });
        for (const auto i : views::iota(0U, N))
            sorted_index_inv[sorted_index[i]] = i;
        for (auto &&[_m, p, _s, _g] : vertex)
            p = sorted_index_inv[p];
        for (auto &&[i, _] : potion)
            i = sorted_index_inv[i];
        for (const auto i : views::iota(0U, N)) {
            const auto j{sorted_index_inv[i]};
            swap(sorted_index_inv[sorted_index[i]], sorted_index_inv[sorted_index[j]]);
            swap(sorted_index[i], sorted_index[j]);
            swap(vertex[sorted_index[i]], vertex[sorted_index[j]]);
        }
        ranges::sort(potion, {}, [](auto &&i) { return i.index; });
        for (unsigned x{}; auto &&[m, p, strength, gain] : vertex) {
            m = 0;
            m = vertex[p].potion_mask;
            if (!gain)
                m |= 1U << x++;
        }
    }

    const auto K{size(potion)};
    // dp[S] := Maximum reachable strength when all the medicine in S are taken but the others are not
    vector<unsigned long> dp(1U << K);

    // vertex reachable without taking the medicine
    dp[0] = 0;
    for (const auto &[_m, _p, need_strength, gain] : vertex | views::filter([](auto &&i) { return i.potion_mask == 0; }))
        if (need_strength <= dp[0])
            dp[0] = min(dp[0] + gain, max_strength + 1);
        else
            break;

    for (const auto drunk_potions : views::iota(0U, 1U << K)) {
        const auto prev_strength{dp[drunk_potions]};
        // medicine that can be taken
        const auto reachable_potion{[prev_strength, &vertex, &potion](auto i) {
            return vertex[potion[i].index].need_strength <= prev_strength;
        }};
        // medicine that are not taken yet
        const auto remaining_potion{[drunk_potions](auto i) { return !(1 & (drunk_potions >> i)); }};
        for (const auto next_potion : views::iota(0U, K) | views::filter(reachable_potion) | views::filter(remaining_potion)) {
            auto now_strength{min(prev_strength * potion[next_potion].multiply, max_strength + 1)};
            const auto next_potions{drunk_potions | (1U << next_potion)};
            // vertex reachable without taking medicine
            const auto reachable_vertex{[next_potions](auto &&v) { return !(v.potion_mask & ~next_potions); }};
            // vertex not erased yet
            const auto remaining_vertex{[drunk_potions, prev_strength](auto &&v) {
                return (v.potion_mask & ~drunk_potions) || prev_strength < v.need_strength;
            }};
            for (const auto &[_m, _p, need_strength, gain] : vertex | views::filter(reachable_vertex) | views::filter(remaining_vertex))
                if (need_strength <= now_strength)
                    now_strength = min(now_strength + gain, max_strength + 1);
                else
                    break;
            dp[next_potions] = max(dp[next_potions], now_strength);
        }
    }

    cout << (dp.back() < vertex.back().need_strength ? "No" : "Yes") << endl;

    return 0;
}

posted:
last update: