предложить алгоритм для следующей головоломки! - PullRequest
6 голосов
/ 22 февраля 2011

Есть n бензобаков, расположенных по кругу.Каждая койка отделена от остальных на определенное расстояние.Вы выбираете какой-либо способ передвижения, для которого требуется 1 литр бензина на расстояние 1 км.Вы не можете бесконечно брать любое количество бензина из каждой койки, поскольку каждая койка имеет только ограниченный бензин.Но вы знаете, что сумма литров бензина во всех койках равна пройденному расстоянию.

, т. Е. Пусть P1, P2, ... Pn будут n койками, расположенными по кругу.d1 - расстояние между p1 и p2, d2 - расстояние между p2 и p3.dn - это расстояние между pn и p1.Теперь выясните, откуда можно начать движение, чтобы в вашем режиме движения никогда не заканчивалось топливо.

Ответы [ 8 ]

4 голосов
/ 22 февраля 2011

Существует алгоритм O (n).

Предположим, что v [0] = p1 - d1, v [1] = p2 - d2, ..., v [n-1] = pn - dn. Все, что нам нужно сделать, это найти начальную точку i, так что вся частичная сумма будет не меньше 0, то есть v [i]> = 0, v [i] + v [(i + 1)% n]> = 0, v [i] + v [(i + 1)% n] + v [(i + 2)% n]> = 0, ..., v [i] + ... + v [(i + n-1)% n]> = 0.

Мы можем найти такую ​​начальную точку, вычислив s [0] = v [0], s [1] = v [0] + v [1], s [2] = v [0] + v [1 ] + v [2], ..., s [n-1] = v [0] + ... + v [n-1] и подобрать минимум s [k]. Тогда индекс (k + 1)% n является начальной точкой.

Доказательство. Предположим, что минимальным элементом является s [k]. По описанию проблемы должно быть минимум s [k] <= 0. </p>

Поскольку общая сумма v [0] + v [1] + ... + v [n-1] = 0, мы имеем v [k + 1] + v [k + 2] + ... v [n-1] = -s [k]> = 0, и невозможно, чтобы v [k + 1] + ... v [j] <0 (k <j <n). (Потому что если v [k + 1] + ... v [j] <0, то s [j] <s [k], что противоречит предположению, что s [k] минимально.) Таким образом, мы имеем v [k + 1]> = 0, v [k + 1] + v [k + 2]> = 0, ..., v [k + 1] + v [k + 2] + ... + v [ n-1]> = 0.

Поскольку s [k] является минимальным, у нас также есть v [k + 1] + v [k + 2] + ... + v [n-1] + v [0] = -s [k ] + v [0] = -s [k] + s [0]> = 0, -s [k] + v [0] + v [1] = -s [k] + s [1]> = 0 , ..., -s [k] + v [0] + v [1] + ... + v [k-1] = -s [k] + s [k-1]> = 0. Так что все сумма, начинающаяся с (k + 1), составляет не менее 0. КЭД.

2 голосов
/ 22 февраля 2011

Это делает то, что делают некоторые из других ответов - находит минимум "графика", созданного дельтами net-change-in-petrol, когда вы кружите вокруг. В зависимости от того, с чего мы начинаем, точные значения могут перемещаться равномерно вверх или вниз по сравнению с какой-либо другой стартовой позицией, но индекс минимального значения всегда является значимым показателем того, где мы можем начать, и знаем, что у нас никогда не кончится бензин , Эта реализация пытается минимизировать использование памяти и завершается за O (n).

#include <iostream>

int dist[] = { 3, 10, 2, 4, 6, 9 };
int refill[] = { 3, 4, 6, 3, 7, 11 };

static const int n = sizeof dist / sizeof *dist;

int main()
{
    int cum = 0, min = 0, min_index = 0;
    for (int i = 0; i < n; ++i)
    {
        cum += refill[i] - dist[i];
        std::cout << cum << ' ';
        if (cum <= min)
        {
            min = cum;
            min_index = i;
        }
    }
    std::cout << "\nstart from index " << (min_index + 1) % n << " (0-based)\n";
}

Посмотрите, как работает на ideone.com

2 голосов
/ 22 февраля 2011

Давайте выберем нежелательный алгоритм, который, как мы знаем, неверен, чтобы понять, почему он неправильный ...

нотации ...

Текущая точка: (галлоны газа в текущей точке, галлоны, необходимые для создания следующей точки) -> Оставшийся газ (галлоны)

В немного более математической форме:

P [i]: (g (P [i]), d (P [i + 1])) -> сумма (g (P [i]) - d (P [i + 1]))) от i = 1 до текущей точки-1

(А теперь плохой алгоритм ...)

P1: (2,2) -> 0 (at P2)
P2: (5,3) -> 2 (at P3)
P3: (2,4) -> 0 (at P4)
P4: (2,5) -> -3 (ran out of gas 3 miles short of P5)

Чтобы добраться до P5, нам нужно иметь три дополнительных галлона газа к тому времени, когда мы дойдем до P3, и чтобы иметь 3 дополнительных галлона на P3, нам нужно иметь 3 дополнительных галлона на P1:

??? -> +3 (at P1)
P1: (2,2) -> 0+3 = 3 (at P2)
P2: (5,3) -> 2+3 = 5 (at P3)
P3: (2,4) -> 0+3 = 3 (at P4)
P4: (2,5) -> -3 +3 = 0 (made it to P5)

Поэтому хитрость заключается в том, чтобы найти самые худшие участки - там, где вам не дают достаточно газа, чтобы пройти их. Мы знаем , мы не можем начать с P4, P3, P2 или P1. Мы должны начать где-то раньше и накопить достаточно газа, чтобы пройти через плохой участок.

Без сомнения, в круге будет много плохих участков, что несколько усложнит, но на самом деле довольно легко понять, как это сделать.

возможно , что следующие несколько точек после самого худшего отрезка в круге могут быть пройдены после отрезка, но только если они не изменят ваши запасы газа. (например, точка после наихудшего растяжения дает вам 2 галлона газа и заставляет вас пройти 2 галлона расстояния до следующей точки.)

В некоторых случаях, однако, худший раздел ДОЛЖЕН быть последним. Это потому, что перед тем, как вы начнете работать с этим разделом, вам нужно накопить как можно больше газа, и следующий пункт после худшего отрезка может дать вам последний кусок газа, который вам нужен, а это значит, что вам нужно пройти его до того, как брать на худшем отрезке. Хотя может существовать несколько решений, простой факт заключается в том, что обход самого худшего раздела - это ВСЕГДА решение. Вот некоторый код:

class point_ {
int gasGiven_;
int distanceToNextPoint_;

public:
int gasGiven() {return gasGiven_;}
int distanceToNextPoint {return distanceToNextPoint_;}
}

class Circle_ {
public:
numberOfPoints;
point_ *P;
}

В основном ():

int indexWorstSection=0;
int numberPointsWorstSection=0;
int worstSum=0;
int currentSum=0;
int i=0; 
int startingPoint =0;

// construct the circle, set *P to malloc of numberOfPoints point_'s, fill in all data

while (i<(Circle.numberOfPoints-1) || currentSum<0)
   {
   currentSum += Circle.P[i].gasGiven() - Circle.P[i].distanceToNextPoint();

   if (currentSum < worstSum) { worstSum = currentSum; indexWorstSection=i-numberPointsWorstSection; startingPoint=i;}

   if (currentSum>0) { currentSum=0; } 
     else { numberPointsWorstSection++; }

   if (i==(Circle.numberOfPoints-1)) { i=0; }
     else { i++; }
   }

if (indexWorstSection<0) indexWorstSection=Circle.numberOfPoints+indexWorstSection;

Причина, по которой вы не можете сделать это циклом for, состоит в том, что наихудший раздел может быть, например, от i = (Circle.numberOfPoints -2) до i = 3. Если значение currentSum меньше нуля, оно должно продолжить в начале массива.

Не пробовал код и не занимался серьезным программированием почти десятилетие. Извините, если есть ошибки. Вам, несомненно, придется немного почистить это.

1 голос
/ 13 декабря 2011

Вот подход, который работает в O(n) времени и O(1) пространстве.Начните с любой станции, назовите ее станцией 0 и двигайтесь вперед, пока не закончится бензин.Если у вас нет бензина, готово.В противном случае, если вы выбежали между станциями k и k+1, начните заново со станции k+1.Запишите, если вы снова пройдете 0, и если после этого у вас не получится.

Причина, по которой это работает, заключается в том, что если вы начинаете со станции i и заканчиваете бензинмежду станциями k и k+1, то до станции k+1 у вас также закончится бензин, если вы начнете с любой станции между i и k.

Вот алгоритм с учетоммассивы P (бензин) и D (расстояние):

int position = 0;
int petrol = P[0];
int distance = D[0];

int start = 0;
while (start < n) {
    while (petrol >= distance) {
        petrol += P[++position % N] - distance;
        distance = D[position % N];
        if (position % N == start)
            return start;
    }
    start = position;
    petrol = P[start];
}
return -1;
1 голос
/ 22 февраля 2011

Каждый этап поездки оказывает чистое влияние на топливо, добавляя из хранилища и используя, чтобы совершить поездку.Все, что вам нужно сделать, это пройти один раз, отслеживая уровень топлива, когда вы достигнете каждой точки, даже если она отрицательная.Следите за наименьшим уровнем топлива и точкой, в которой он произошел.Если вы начнете с этого момента, вы сможете обойти это без топлива.Это предполагает, что вы начинаете с пустого резервуара и получаете газ только с того места, где начинаете, а также что вы всегда можете взять весь газ, вы никогда не наполнитесь и вам придется оставить газ позади.

Допустим, у вас есть 5 баллов, от P1 до P5:

Point  Fuel  Distance to next point
P1     5     8
P2     3     4
P3     12    7
P4     1     4
P5     7     5

Если вы выберете P1, то загрузитесь на 5 единиц топлива, а поездка на P2 оставляет вас с запасом топлива -3.Далее вы получите следующие цифры:

Point  Fuel Balance (before refueling)
P1     0
P2     -3
P3     -4
P4     1
P5     -2
P1     0

Так что, если вы начнете с самого низкого значения, P3, вы можете вернуться к нему (топливо 0 для запуска, 5 на P4, 2 на P5, 4 на P11 на P2, 0 назад на P3)

float [] storedFuel = { 1, 1, 1, 1, 1, 1 };
float [] distance = { 1, 1, 1, 1, 1, 1 };
int n = 6;

int FindMinimumPosition()
{
    float fuel = 1;
    int position = 0;
    float minimumFuel = 1;
    int minimumPosition = 0;

    while (position < n)
    {
        fuel += storedFuel[position];
        fuel -= distance[position];
        position++; // could be n which is past array bounds
        if (fuel < minimumFuel) {
            minimumFuel = fuel;
            minimumPosition = position % n;
        }
    }

    return minimumPosition
}
0 голосов
/ 01 августа 2013

Вот решение O (n)

private int findStartingPoint(int[] petrol, int[] dist, int[] mileage){
    int curPoint = 0;
    boolean done = false;
    while(curPoint < petrol.length && !done)
    {
        int prevPoint = curPoint;
        int nextPoint = (curPoint+1) % petrol.length;
        int nextSolutionPoint = curPoint + 1;
        int totalPetrol = 0;
        while(nextPoint != curPoint){
            totalPetrol += petrol[prevPoint] - (dist[prevPoint]/mileage[prevPoint]);
            if(totalPetrol < 0){
                curPoint = nextSolutionPoint;
                break;
            }
            prevPoint = nextPoint;
            nextPoint = (nextPoint + 1) % petrol.length;
            nextSolutionPoint++;
        }
        if(curPoint == nextPoint){
            return curPoint;
        }
    }
    return -1;
}

}

0 голосов
/ 22 февраля 2011

Для каждого разрыва найдите прибыль или убыток, полученный путем заполнения в койке перед разрывом, а затем пересечения разрыва.В произвольной точке определите общее количество бензина, оставшегося в каждой точке полного круга, принимая, что он может быть отрицательным в некоторой точке.

Теперь многократно циклически меняйте это решение.Удалите информацию для последней точки, которая теперь будет первой точкой.Установите смещение, чтобы учесть тот факт, что вы теперь начинаете на одну точку назад и будете больше (или меньше) бензина в каждой оставшейся точке.Добавьте информацию для новой точки с учетом смещения.Это решение возможно, если минимальное количество бензина в любой точке плюс смещение больше нуля.

Вы можете использовать какую-либо структуру данных log n (отсортированная карта, очередь приоритетов ...) дляследите за минимумом, чтобы снизить стоимость до n log n.

0 голосов
/ 22 февраля 2011

От макушки головы, вот алгоритм, который должен работать:

  1. let e1 = (количество бензина в P1) - d1 (то есть избыток бензина в P1 по сравнению с тем, что необходимо для поездки в P2), и аналогично для e2, ..., en. (Эти цифры могут быть положительными или отрицательными.)
  2. Сформировать частичные суммы s1 = e1; s2 = e1 + e2; ..., sn = e1 + e2 + ... + en. Из условий задачи мы знаем, что sn = 0.

Задача теперь состоит в том, чтобы найти круговую перестановку койок (или, проще говоря, значений e), чтобы ни одно из значений s не было отрицательным. Можно просто многократно сдвигать единицу, обновляя значения s, пока не будет найдено решение. (Не сразу очевидно, что всегда есть решение, но я думаю, что оно есть. Если вы сдвинетесь n раз, не найдя решения, то вам гарантировано, что его нет.)

Это алгоритм O (n ^ 2) - не очень хороший. Хорошая эвристика (возможно, точное решение) может состоять в том, чтобы сдвигать так, чтобы отрицательное значение s наибольшей величины сдвигалось в положение n.

...