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

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

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

Во входном файле до 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
  • Проголосовать: не нравится

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

struct orcState {

  • »
    »
    9 лет назад, скрыть # ^ |
     
    Проголосовать: нравится 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.

»
9 лет назад, скрыть # |
 
Проголосовать: нравится 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';
    }
}
»
9 лет назад, скрыть # |
← 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.