Блог пользователя dmkz

Автор dmkz, история, 7 лет назад, По-русски

Не знаю, как правильно решить следующую задачу. Есть идеи, но они не проходят. Кто знает, направьте в верное русло, пожалуйста. Также, если знаете похожие задачи, то поделитесь ссылками на них. Оригинальная формулировка задачи с тестами доступна здесь

Во входном файле до 50 тестов. Каждый тест — отдельная подзадача. Нужно вывести ответ для каждой подзадачи с новой строки. Подзадача состоит в следующем:

На нашем пути есть от 1 до 20 засад, в каждой засаде сидят k[i] разбойников (от 1 до 1000), чтобы они нас пропустили, нужно c[i] монет (от 1 до 1000). Изначально в нашем отряде 0 наемников.

Мы можем:

  1. Нанять бандитов, сидящих в засаде, потратив 2 * c[i] монет

  2. Заплатить бандитам c[i] монет, чтобы они нас пропустили

  3. Сразиться с ними. Погибает одинаковое количество с обеих сторон. Причем у нас те наемники, которые были наняты раньше, погибают первыми. Но наемники не выдерживают больше трех битв. После третьей битвы они уходят от нас, если выживают.

Необходимо проехать все засады, потратив наименьшую сумму денег. В ответ выдать эту сумму денег.

Ограничения: 3 секунды, 256 МБ на тест, таким образом, 0.06 секунд и 256 МБ на одну подзадачу.

Думал в сторону двумерного ДП. Параметры:

  • количество пройденных засад

  • набранная сумма

Для состояния dp[k][sum] хранить оптимальную тройку (n1, n2, n3) — сколько наемников могут участвовать в одной битве, двух и трех соответственно. Для недостижимых позиций ( - 1,  - 1,  - 1). Но не ясно, как выбирать из двух возможных троек лучшую при одинаковых переходах. Если выбирать по сумме, то тройка (1000, 0, 0) окажется лучше тройки (0, 0, 999). но все наемники убегут после первой битвы. Если выбирать по компонентам, то (0, 0, 2), окажется лучше, чем (999, 0, 1), но тогда не победить 1000 разбойников. Поэтому вариант с тройками не проходит, а больше адекватных идей нет.

  • Проголосовать: нравится
  • +12
  • Проголосовать: не нравится

»
7 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

Выгодно ли нам вообще нанимать бандитов? Ведь если я правильно понял задачу, заплатив 2*x мы получаем возможность в лучшем случае преодолеть еще x последующих бандитов бесплатно, что в лучшем случае эквивалентно тому, что мы заплатим всем этим бандитам за то, чтоб они нас пропустили те же 2*x денег. А потому мы просто можем никого не нанимать и заплатить всем — в таком случае ответом на каждый тест будет просто сумма массива и DP не нужно.

  • »
    »
    7 лет назад, # ^ |
      Проголосовать: нравится +5 Проголосовать: не нравится

    Нет, не так. У каждых бандитов своя стоимость покупки и свое количество. То есть, для засад i и j стоимость прохода задана в тесте как c[i] и c[j] (могут не совпадать), а количество бандитов k[i] и k[j] (могут не совпадать).

  • »
    »
    7 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится

    К посту добавил оригинальную формулировку задачи с примерами входных / выходных данных

»
7 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

Автокомментарий: текст был обновлен пользователем dmkz (предыдущая версия, новая версия, сравнить).

»
7 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

Можно хранить в состоянии суммарное число нанятых бандитов(а сумму видимо ответом динамики). Мы всегда знаем, что это последние бандиты, которых можно было нанять (почему?). То есть по факту нужно решать как будто без ограничения на кол-во бандитов, но при этом при каждом k делать bandits = min(bandits, K[k] + K[k - 1] + K[k - 2])

  • »
    »
    7 лет назад, # ^ |
      Проголосовать: нравится +3 Проголосовать: не нравится

    Если я правильно вас понял, то вот тест, где не 3 последних группы выгоднее всего нанимать:

    c: 1 1 1 1 1000
    
    k: 1000 1 1 1 1000
    
    • »
      »
      »
      7 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится

      Да, понял в чем проблема.

      А если дополнительно к количеству хранить маску длины 3, где мы кого-то нанимали? А таком случае нужно всегда тратить их жадно со старых, а значит вроде понятно, сколько человек пропадает при переходе

      • »
        »
        »
        »
        7 лет назад, # ^ |
          Проголосовать: нравится +16 Проголосовать: не нравится

        Возможно, я что-то не понял в идее решения, но вот тест:

        с = [1, 1, 1, 1, 1, 1000]
        k = [200, 200, 200, 200, 200, 1000]
        

        Нужно собрать все 5 первых групп, чтобы побить последнюю (на мораль наёмников влияют только бои).

»
7 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

Auto comment: topic has been translated by dmkz (original revision, translated revision, compare)

»
7 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

Auto comment: topic has been updated by dmkz (previous revision, new revision, compare).

»
7 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

struct orcState {

  • »
    »
    7 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится

    Can you describe the algorithm with the orcs (bandits) states in details for evaluate complexity of solution? Naive with states for each groups get TLE because 320 = 3486784401 is very large number of states.

»
7 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

The following C++17 solution may have a reasonable execution time.

#include <bits/stdc++.h>

using namespace std;

struct hired_t
{
    int battles, size;

    hired_t( int s ) : battles( 3 ), size( s ) {}
};

typedef vector< hired_t > vector_t;

struct state_t: vector_t
{
    int cost, passed, hired;

    state_t( int c = 0, int p = 0, int h = 0 ) : cost( c ), passed( p ), hired( h ) {}

    bool operator < ( const state_t &s ) const
    {
        if ( cost != s.cost )
            return ( cost < s.cost );

        if ( passed != s.passed )
            return ( passed < s.passed );

        return hired > s.hired;
    }

    void battle( int t )
    {
        int u; stack< vector_t::iterator > erased;

        for( auto it = begin(), iu = end(); it != iu; it++, t -= u, hired -= u )
            if ( ( it->size -= ( u = min( t, it->size ) ) ) == 0 || --(it->battles) == 0 )
            {
                erased.push( it );

                if ( it->size > 0 )
                    hired -= it->size;
            }

       while( !erased.empty() )
            erase( erased.top() ), erased.pop();
    }

    void hire( int t )
    {
        emplace_back( t ), hired += t;
    }

} initial_state;

struct states_t: set< state_t >
{
    int min_cost, N, next; struct group_t { int size, cost; } g, group[ 20 ];

    states_t(): min_cost( INT_MAX )
    {
        cin >> N, insert( initial_state );

        for( int i = 0; i < N; i++ )
            cin >> group[ i ].size >> group[ i ].cost;
    }

    void update( const state_t &present )
    {
        if ( present.cost >= min_cost )
            return;

        min_cost = present.cost;

        for( auto it = rbegin(); it != rend() && it->cost > min_cost; it = rbegin() )
            erase( find( *it ) );
    }

    void save( const state_t &present )
    {
        if ( present.cost < min_cost )
            insert( present );
    }

    void battle( state_t present )
    {
        if ( present.passed < N )
            present.battle( g.size ), save( present );
        else
            update( present );
    }

    void pass( state_t present )
    {
        present.cost += g.cost;

        if ( present.passed < N )
            save( present );
        else
            update( present );
    }

    void hire( state_t present )
    {
        present.cost += 2 * g.cost, present.hire( g.size ), save( present );
    }

    void iterate()
    {
        auto it = begin(); state_t present( *it );

        erase( it ), g = group[ present.passed++ ];

        if ( present.hired >= g.size )
            battle( present );

        pass( present );

        if ( present.passed < N )
            hire( present );
    }
};

int main()
{
    ios_base::sync_with_stdio( false ), cin.tie( nullptr ), cout.tie( nullptr );

    int T; cin >> T;

    while( T-- )
    {
        states_t states;

        while( !states.empty() )
            states.iterate();

        cout << states.min_cost << '\n';
    }
}
»
7 лет назад, # |
Rev. 31   Проголосовать: нравится -10 Проголосовать: не нравится

The following Solution seems to have a reasonable execution time.

Starting with a set of candidate partial solutions that contains a null initial state and an INT_MAX upper bound for the minimum cost, the required solution is the value of the upper bound when the set of partial solutions is empty. The iterative progress through the groups adds a new partial solution to the set only if its present cost is less than the present upper bound.

Hope that this helps.

  • »
    »
    7 лет назад, # ^ |
    Rev. 4   Проголосовать: нравится 0 Проголосовать: не нравится

    Beautiful code style! But this is not full solution.

    WRONG ANSWER: This counter-test — WA is 7, but true answer is 6:

    1. hire. state = (0, 0, 2), amount = 2.
    2. hire. state = (0, 0, 4), amount = 4.
    3. pass. state = (0, 0, 4), amount = 5.
    4. pass. state = (0, 0, 4), amount = 6.
    5. battle. state = (0, 3, 0), amount = 6.
    6. battle. state = (2, 0, 0), amount = 6.
    7. battle. state = (0, 0, 0), amount = 6.

    TIME LIMIT EXCEEDED: This counter-test takes greater then one minute (65.959 sec) — TLE. In this test, each of the 50 sets of input data is different.

    This problem from coding interview in Samsung company.

    • »
      »
      »
      7 лет назад, # ^ |
        Проголосовать: нравится -7 Проголосовать: не нравится

      I don't know how to solve it normal, but wrote the same brute O(3n). It isn't TLing for 50 random testcases you provided, also works on samples. I believe, that this solution may works quite good on random testcases, but can be hacked with some special situations.

      https://ideone.com/qAsgd6

      • »
        »
        »
        »
        7 лет назад, # ^ |
        Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

        Thanks! This is simple TLE test. We get TLE when we can fight with each group from each state.

        Maybe normal solution is recursion with memoization or partial memoization or dynamic programming over subsets. Maybe there are very few very worst tests, and we can make a preliminary calculation.

        • »
          »
          »
          »
          »
          7 лет назад, # ^ |
            Проголосовать: нравится 0 Проголосовать: не нравится

          edited!

          • »
            »
            »
            »
            »
            »
            7 лет назад, # ^ |
              Проголосовать: нравится 0 Проголосовать: не нравится

            Nice! This is not O(3n), because in the sequence of actions, a word "fight" can not be contained more than three times in a row.

          • »
            »
            »
            »
            »
            »
            7 лет назад, # ^ |
              Проголосовать: нравится 0 Проголосовать: не нравится

            1.340.895.616 in worst case if we not hire last group, not fight with first group and not fight more than 3 times in row

            • »
              »
              »
              »
              »
              »
              »
              7 лет назад, # ^ |
              Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

              Comment below. It's will works bad (O(3n)), when solution is hire all, but fight last.

              • »
                »
                »
                »
                »
                »
                »
                »
                7 лет назад, # ^ |
                Rev. 2   Проголосовать: нравится +6 Проголосовать: не нравится

                On test

                1
                4
                1 1
                1 1
                1 1
                3 1000
                

                your code gives incorrect answer 1003 (because you modify variables orc1, orc2, orc3 when you choose "fight" option, but you then use new values instead of old in other options). After fixing this bug, your code works >2s on my computer on this single test:

                1
                20
                1 1
                1 1
                ...
                1 1
                19 1000
                

                And shuffling transition order won't help, because answer is 38 and it is impossible to spend more than 38 coins on first 19 operations no matter what you do, so you never actually cut any useless branches.

              • »
                »
                »
                »
                »
                »
                »
                »
                7 лет назад, # ^ |
                Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

                3.46 sec and 1 327 729 094 calls on this single test after fix bug

    • »
      »
      »
      7 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится

      Thanks for the kind words.

      For the first counter-test, replacing set< state_t > in line 48 with multiset< state_t > generated the expected output.

      The second counter-test with TLE result requires further checking.