Pintreepi1Lmax — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
м (rollbackEdits.php mass rollback)
 
(не показано 47 промежуточных версий 3 участников)
Строка 4: Строка 4:
 
|definition=Рассмотрим задачу на нахождение расписания:
 
|definition=Рассмотрим задачу на нахождение расписания:
 
# У нас есть несколько станков, работающих параллельно. У станков могут быть разные скорости выполнения работ.
 
# У нас есть несколько станков, работающих параллельно. У станков могут быть разные скорости выполнения работ.
# Есть несколько заданий, каждое из которых имеет определенный порядок, который указан в направленном из корней в лист intree-дерева.
+
# Есть несколько заданий, каждое из которых имеет определенный порядок, который указан в направленном из корней в лист [[Классификация задач#intree|intree-дерева]], которое имеет несколько корней и один лист.
 
# Любая работа на любом станке выполняется единицу времени.
 
# Любая работа на любом станке выполняется единицу времени.
 
Требуется минимизировать максимальное опоздание <tex>L_{max} = \max\limits_i \{C_i - d_i\}</tex>.
 
Требуется минимизировать максимальное опоздание <tex>L_{max} = \max\limits_i \{C_i - d_i\}</tex>.
Строка 13: Строка 13:
 
=== Идея ===
 
=== Идея ===
  
Все вершины хранятся в дереве (англ. intree), которое имеет несколько корней и один лист.
+
Все работы хранятся в качестве вершин [[Классификация задач#intree|intree-дерева]], состоящем из <tex>n</tex> вершин. В intree-дереве у одной вершины может быть два и более родителей.
 +
Решение задачи состоит из двух шагов:
  
Работы хранятся в дереве, состоящем из <tex>n</tex> вершин с фиктивной нулевой работой, которая является родителем тех вершин, у которых изначально его не было. В intree-дереве у одной вершины может быть два и более родителей.
+
# Меняем делайны работ в соответствии с их очередностью: для всех <tex>i, j</tex> таких, что существует ребро из <tex>i</tex> в <tex>j</tex> будем менять <tex>{d_i}</tex> на <tex>\min ({d_i}, {d_j} - 1) </tex>.  
Решение задачи состоит из двух шагов: на первом шаге мы меняем сроки выполнения работ в соответствии с их очередностью.
+
# Работы расставляются в неубывающем порядке их дедлайнов.
  
На первом шаге изменения сроков состоит в следующем: для всех <tex>i, j</tex> таких, что существует ребро из <tex>i</tex> в <tex>j</tex> будем менять <tex>{d_i}</tex> на <tex>\min ({d_i}, {d_j} - 1) </tex>. На втором шаге работы расставляются в неубывающем порядке сроков.
+
=== Псевдокод ===
 +
==== Первый шаг ====
 +
На первом шаге мы релаксируем дедлайны всех работ, кроме листовой, в соответствии с предыдущим пунктом.
 +
* В массиве <tex>\mathtt d</tex> хранятся дедлайны работ.
 +
* В массиве <tex>\mathtt {parents}</tex> {{---}} массив предков <tex>i</tex>-й работы.
 +
* В переменной <tex>\mathtt i</tex> хранится номер листа (он один, см. условие задачи).
 +
'''Deque<int>''' deque = <tex>\varnothing</tex>
 +
deque.push(i)
 +
'''while''' '''not''' deque.isEmpty
 +
    '''int''' j = deque.removeFirst()
 +
    '''for''' k '''in''' parents[j]
 +
        d[k] = min(d[k], d[j] - 1)
 +
        deque.addLast(k)
  
=== Первый шаг ===
+
==== Второй шаг ====
Алгоритм изменения сроков:
+
На втором этапе алгоритма работы сортируются в неубывающем порядке их дедлайнов. Предполагается, что работы будут занумерованы так, что <tex>d_{i} \leqslant d_{j}</tex>, если <tex>i \leqslant j</tex>.
deque = i <tex>\mid</tex> i является листом
+
* В переменной <tex>\mathtt F</tex> хранится время, когда какой-либо станок освободится.
while deque not empty
+
* В массиве <tex>\mathtt r</tex> хранится информация о максимальном времени завершении обработки родителя.
    i = stack.remove_first()
+
* Массив <tex>\mathtt q</tex> хранит информацию о количестве работ, готовых к исполнению (находящихся в очереди) в момент времени <tex>t</tex>.
    for j <tex>\mid</tex> j является предком i
+
* Массив <tex>\mathtt x</tex> хранит информацию о начале выполнения работы <tex>i</tex>.
        <tex>d_{j} = \min(d_{j}, d_{i} - 1)</tex>
+
* В массиве <tex>\mathtt {child}</tex> хранится индекс ребенка <tex>i</tex>-й работы.
        stack.add_last(j)
 
   
 
{{Лемма
 
|statement=
 
Работа с новым сроком <tex>{d'_i}</tex> в расписании не имеет опозданий тогда и только тогда, когда она не имела опозданий с оригинальным сроком <tex>{d_i}</tex>.
 
|proof=
 
<tex>\Rightarrow </tex>: Т.к. <tex>{d'_i} \leqslant {d_i}</tex>, значит, если опозданий не было со значениями <tex>{d'_i}</tex>, их не будет и со значениями <tex>{d_i}</tex>.
 
 
 
<tex>\Leftarrow </tex>: Пусть у нас были сроки <tex>{d_i}</tex> и мы их заменили на <tex>{d'_i}</tex> в соответствии с приведенным алгоритмом.
 
:Пронумеруем вершины от <tex>1</tex> до <tex>n</tex> в соответствии с '''обратным''' порядком обхода в алгоритме изменения сроков, причём <tex>d_{i} \leqslant d_{j}</tex>, если <tex>i \leqslant j</tex>. В соответствии с расписанием, время, когда деталь закончит обрабатываться на станке <tex>{C_i}</tex> удовлетворяет неравенству <tex>{C_i} \leqslant {d_i}</tex> для всех <tex>{C_1} \dots {C_n}</tex>. Тогда мы имеем  <tex>{C_n} \leqslant {d_n} = {d'_n}</tex>. Если для какого-то <tex>1 < r \leqslant n</tex> мы имеем <tex>{C_n} \leqslant {d'_n}</tex> для <tex>i = r \dots n </tex> и существует работа <tex>j</tex> из этого промежутка, что вершина с номером <tex>r - 1</tex> является ее родителем, тогда <tex>C_{r-1} \leqslant  \min(d_{r-1},d'_{j}-1) = d'_{r-1}</tex>
 
}}
 
 
 
=== Второй шаг ===
 
На втором этапе алгоритма работы сортируются в неубывающем порядке их дедлайнов. Предполагается, что работы занумерованы в соответствии с предыдущим пунктом, т.е. <tex>d_{i} \leqslant d_{j}</tex>, если <tex>i \leqslant j</tex>.
 
 
 
В переменной <tex>F</tex> хранится время, когда станок освободится.
 
 
 
В массиве <tex>r</tex> хранится информация о максимальном времени завершении обработки родителя.
 
 
 
Массив <tex>q</tex> хранит информацию о количестве работ, готовых к исполнению (находящихся в очереди) в момент времени <tex>t</tex>.
 
 
 
Массив <tex>x</tex> хранит информацию о начале выполнения работы <tex>i</tex>.
 
  
 
  F = 0
 
  F = 0
  for i = 1 .. n
+
  '''for''' '''int''' i = 1 .. n
 
     r[i] = 0
 
     r[i] = 0
  for t = 0 .. n
+
  '''for''' '''int''' t = 0 .. n
 
     q[t] = 0
 
     q[t] = 0
  for i = 1 .. n
+
  '''for''' '''int''' i = 1 .. n
     t = max(r[i], F)
+
     '''int''' t = max(r[i], F)
 
     x[i] = t
 
     x[i] = t
 
     q[t] = q[t] + 1
 
     q[t] = q[t] + 1
     if q[t] == m
+
     '''if''' q[t] == m
 
       F = t + 1
 
       F = t + 1
     j = i.child()
+
     '''int''' j = child[i]
     r[j] = max (r[j], t + 1)
+
     r[j] = max(r[j], t + 1)
  
 +
В результате ответ можно получить, зная конечный массив <tex>\mathtt x</tex> и делайны работ: <tex>L_{max} = \max\limits_i (\mathtt x[i] + 1 - d_{i}</tex>), так как все работы выполняются единицу времени, следовательно, <tex>C_{i} = \mathtt x[i] + 1</tex>. Можно заметить, что при вычислении ответа неважно, какие дедлайны использовать, начальные или релаксированные, потому что для любого <tex>k</tex> и его предка <tex>i</tex> либо производится релаксация и выполняется равенство <tex> d_{k} = d_{i} - 1</tex>, а значит, после релаксации максимум не изменится, поскольку при замене дедлайна на меньший максимум увеличится, а новое значение <tex>L_{k}</tex> будет равно <tex>L_{i}</tex>, либо мы не делали релаксацию, и значение <tex>d_{k}</tex>, и, следовательно, <tex>L_{k}</tex> не поменяются.
 +
 +
=== Доказательство корректности ===
 +
==== Первый шаг ====
 +
{{Лемма
 +
|statement=
 +
Работа с новым сроком <tex>{d'_i}</tex> в расписании не имеет опозданий тогда и только тогда, когда она не имела опозданий с оригинальным сроком <tex>{d_i}</tex>.
 +
|proof=
 +
<tex>\Rightarrow </tex>
 +
:Т.к. <tex>{d'_i} \leqslant {d_i}</tex>, значит, если опозданий не было со значениями <tex>{d'_i}</tex>, их не будет и со значениями <tex>{d_i}</tex>.
 +
 +
<tex>\Leftarrow </tex>
 +
:Пусть у нас были сроки <tex>{d_i}</tex> и мы их заменили на <tex>{d'_i}</tex> в соответствии с приведенным алгоритмом.
 +
:Пронумеруем вершины от <tex>1</tex> до <tex>n</tex> в соответствии с '''обратным''' порядком обхода в алгоритме изменения сроков, причём <tex>d_{i} \leqslant d_{j}</tex>, если <tex>i \leqslant j</tex>. В соответствии с расписанием, время, когда деталь закончит обрабатываться на станке <tex>{C_i}</tex> удовлетворяет неравенству <tex>{C_i} \leqslant {d_i}</tex> для всех <tex>{C_1} \dots {C_n}</tex>. Тогда мы имеем  <tex>{C_n} \leqslant {d_n} = {d'_n}</tex>. Если для какого-то <tex>1 < r \leqslant n</tex> мы имеем <tex>{C_n} \leqslant {d'_n}</tex> для <tex>i = r \dots n </tex> и существует работа <tex>j</tex> из этого промежутка, что вершина с номером <tex>r - 1</tex> является ее родителем, тогда <tex>C_{r-1} \leqslant  \min(d_{r-1},d'_{j}-1) = d'_{r-1}</tex>.
 +
}}
 +
 +
==== Второй шаг ====
 
Расписание, сгенерированное этим алгоритмом имеет важное свойство: число заданий в очереди в любой момент времени <tex>t</tex> меньше, чем в момент <tex>t + 1</tex>. Действительно, пусть во время <tex>t</tex> мы выполняем <tex>k</tex> работ, и хотя бы <tex>k + 1 \leqslant m</tex> работ готовы к выполению в момент времени <tex>t + 1</tex>. Но т.к. <tex>k + 1 \leqslant m</tex>, значит каждой из работ предшествовала как минимум одна, поскольку у всех вершин, кроме корней, есть как минимум один предок. Значит, в момент времени <tex>t</tex> исполнялось не менее <tex>k + 1</tex> работ, противоречие.
 
Расписание, сгенерированное этим алгоритмом имеет важное свойство: число заданий в очереди в любой момент времени <tex>t</tex> меньше, чем в момент <tex>t + 1</tex>. Действительно, пусть во время <tex>t</tex> мы выполняем <tex>k</tex> работ, и хотя бы <tex>k + 1 \leqslant m</tex> работ готовы к выполению в момент времени <tex>t + 1</tex>. Но т.к. <tex>k + 1 \leqslant m</tex>, значит каждой из работ предшествовала как минимум одна, поскольку у всех вершин, кроме корней, есть как минимум один предок. Значит, в момент времени <tex>t</tex> исполнялось не менее <tex>k + 1</tex> работ, противоречие.
  
Строка 70: Строка 78:
 
Если существует такое расписание, в котором ни одна из работ не будет выполнена с опозданием, то тогда это свойство сохранится в построенном данным алгоритмом расписании
 
Если существует такое расписание, в котором ни одна из работ не будет выполнена с опозданием, то тогда это свойство сохранится в построенном данным алгоритмом расписании
 
|proof=
 
|proof=
Предположим, что существует работа из <tex>x_{1} \dots x_{n}</tex> расписания, построенного алгоритмом. В таком случае существует работа, которая опоздала по отношению к измененным срокам. Возьмем наименьшее <tex>i</tex> такое, что <tex>x(i) + 1 > d'_{i}</tex>. Пусть <tex>t < d'_{i}</tex> {{---}} наибольшее из удовлетворяющих условию <tex>j < m \mid x(j) = t, d'_{j} \leqslant d'_{i}</tex>
+
Предположим, что существует работа из <tex>x_{1} \dots x_{n}</tex> расписания, построенного алгоритмом. В таком случае существует работа, которая опоздала по отношению к измененным срокам. Возьмем наименьшее <tex>i</tex> такое, что <tex>x(i) + 1 > d'_{i}</tex>. Пусть <tex>t < d'_{i}</tex> {{---}} наибольшее из удовлетворяющих условию <tex>j < m </tex>, где <tex> x(j) = t, d'_{j} \leqslant d'_{i}</tex>
 
Такое <tex>t</tex> существует, потому что иначе <tex>m \cdot d'_{i}</tex> работ <tex>j</tex> с <tex>d'_{j} \leqslant d'_{i}</tex> находятся в очереди до <tex>d'_{i}</tex>. Работа <tex>i</tex> к ним не принадлежит, поскольку <tex>x(i) + 1 > d'_{i}</tex>, а значит, что <tex>m \cdot d'_{i} + 1</tex> должны быть в очереди в момент времени <tex>0 \dots d'_{i}</tex> и ни одна работа не должна опаздывать. Противоречие.
 
Такое <tex>t</tex> существует, потому что иначе <tex>m \cdot d'_{i}</tex> работ <tex>j</tex> с <tex>d'_{j} \leqslant d'_{i}</tex> находятся в очереди до <tex>d'_{i}</tex>. Работа <tex>i</tex> к ним не принадлежит, поскольку <tex>x(i) + 1 > d'_{i}</tex>, а значит, что <tex>m \cdot d'_{i} + 1</tex> должны быть в очереди в момент времени <tex>0 \dots d'_{i}</tex> и ни одна работа не должна опаздывать. Противоречие.
 
Любая работа <tex>j</tex> с <tex>d'_{j} \leqslant d'_{i} </tex> и <tex> x(j) > t </tex> должна иметь предка, начавшего работать в момент времени <tex>t</tex>. Теперь рассмотрим два случая:
 
Любая работа <tex>j</tex> с <tex>d'_{j} \leqslant d'_{i} </tex> и <tex> x(j) > t </tex> должна иметь предка, начавшего работать в момент времени <tex>t</tex>. Теперь рассмотрим два случая:
Строка 83: Строка 91:
 
}}
 
}}
  
 +
==== Корректность алгоритма ====
 
{{Теорема
 
{{Теорема
 
|statement=
 
|statement=
Строка 89: Строка 98:
 
Пусть <tex>L'_{max}</tex> {{---}} оптимальное значение. В таком случае, существует расписание, удовлетворяющее <tex>\max\limits_i \{C_i - d_i\} \leqslant L'_{max}</tex>, что эквивалетно выражению <tex>C_{i} \leqslant d_{i} + L'_{max}</tex> для <tex>i = 1 \dots n </tex>. По первой лемме расписание <tex>S</tex>, построенное для сдвинутых дат <tex>d_{i} + L'_{max}</tex> удовлетворяет данным выражениям. Таким образом, оно оптимально. Нетрудно заметить, что <tex>S</tex> идентично расписанию, построенному алгоритмом, т.к. <tex>(d_{i}+L'_{max})' = d'_{i} + L'_{max} </tex> для <tex>i = 1 \dots n </tex>
 
Пусть <tex>L'_{max}</tex> {{---}} оптимальное значение. В таком случае, существует расписание, удовлетворяющее <tex>\max\limits_i \{C_i - d_i\} \leqslant L'_{max}</tex>, что эквивалетно выражению <tex>C_{i} \leqslant d_{i} + L'_{max}</tex> для <tex>i = 1 \dots n </tex>. По первой лемме расписание <tex>S</tex>, построенное для сдвинутых дат <tex>d_{i} + L'_{max}</tex> удовлетворяет данным выражениям. Таким образом, оно оптимально. Нетрудно заметить, что <tex>S</tex> идентично расписанию, построенному алгоритмом, т.к. <tex>(d_{i}+L'_{max})' = d'_{i} + L'_{max} </tex> для <tex>i = 1 \dots n </tex>
 
}}
 
}}
 +
 +
==== Асимптотика ====
 +
# На первом шаге мы посещаем каждую вершину не более двух раз (первый {{---}} когда ищем вершину без родителя, второй {{---}} когда релаксируем дедлайны) за <tex>O(n)</tex> времени.
 +
# Делаем сортировку вершин за <tex>O(n \log n)</tex>, а затем обходим все вершины по разу и считаем время начала выполнения каждой работы, в сумме за <tex>O(n)</tex>.
 +
Итоговая сложность {{---}} <tex>O(n \log n)</tex>
 +
 +
==См. также==
 +
*[[P2precpi1Lmax|<tex>P2 \mid prec, p_i = 1 \mid L_{\max}</tex>]]
 +
*[[1outtreesumwc | <tex>1 \mid outtree \mid \sum w_i C_i</tex>]]
  
 
==Источники информации==
 
==Источники информации==

Текущая версия на 19:26, 4 сентября 2022

[math]P \mid intree, p_{i} = 1 \mid L_{max}[/math]


Задача:
Рассмотрим задачу на нахождение расписания:
  1. У нас есть несколько станков, работающих параллельно. У станков могут быть разные скорости выполнения работ.
  2. Есть несколько заданий, каждое из которых имеет определенный порядок, который указан в направленном из корней в лист intree-дерева, которое имеет несколько корней и один лист.
  3. Любая работа на любом станке выполняется единицу времени.
Требуется минимизировать максимальное опоздание [math]L_{max} = \max\limits_i \{C_i - d_i\}[/math].


Описание алгоритма

Пример intree-дерева

Идея

Все работы хранятся в качестве вершин intree-дерева, состоящем из [math]n[/math] вершин. В intree-дереве у одной вершины может быть два и более родителей. Решение задачи состоит из двух шагов:

  1. Меняем делайны работ в соответствии с их очередностью: для всех [math]i, j[/math] таких, что существует ребро из [math]i[/math] в [math]j[/math] будем менять [math]{d_i}[/math] на [math]\min ({d_i}, {d_j} - 1) [/math].
  2. Работы расставляются в неубывающем порядке их дедлайнов.

Псевдокод

Первый шаг

На первом шаге мы релаксируем дедлайны всех работ, кроме листовой, в соответствии с предыдущим пунктом.

  • В массиве [math]\mathtt d[/math] хранятся дедлайны работ.
  • В массиве [math]\mathtt {parents}[/math] — массив предков [math]i[/math]-й работы.
  • В переменной [math]\mathtt i[/math] хранится номер листа (он один, см. условие задачи).
Deque<int> deque = [math]\varnothing[/math]
deque.push(i)
while not deque.isEmpty
    int j = deque.removeFirst()
    for k in parents[j]
        d[k] = min(d[k], d[j] - 1)
        deque.addLast(k)

Второй шаг

На втором этапе алгоритма работы сортируются в неубывающем порядке их дедлайнов. Предполагается, что работы будут занумерованы так, что [math]d_{i} \leqslant d_{j}[/math], если [math]i \leqslant j[/math].

  • В переменной [math]\mathtt F[/math] хранится время, когда какой-либо станок освободится.
  • В массиве [math]\mathtt r[/math] хранится информация о максимальном времени завершении обработки родителя.
  • Массив [math]\mathtt q[/math] хранит информацию о количестве работ, готовых к исполнению (находящихся в очереди) в момент времени [math]t[/math].
  • Массив [math]\mathtt x[/math] хранит информацию о начале выполнения работы [math]i[/math].
  • В массиве [math]\mathtt {child}[/math] хранится индекс ребенка [math]i[/math]-й работы.
F = 0
for int i = 1 .. n
   r[i] = 0
for int t = 0 .. n
   q[t] = 0
for int i = 1 .. n
   int t = max(r[i], F)
   x[i] = t
   q[t] = q[t] + 1
   if q[t] == m
      F = t + 1
   int j = child[i]
   r[j] = max(r[j], t + 1)

В результате ответ можно получить, зная конечный массив [math]\mathtt x[/math] и делайны работ: [math]L_{max} = \max\limits_i (\mathtt x[i] + 1 - d_{i}[/math]), так как все работы выполняются единицу времени, следовательно, [math]C_{i} = \mathtt x[i] + 1[/math]. Можно заметить, что при вычислении ответа неважно, какие дедлайны использовать, начальные или релаксированные, потому что для любого [math]k[/math] и его предка [math]i[/math] либо производится релаксация и выполняется равенство [math] d_{k} = d_{i} - 1[/math], а значит, после релаксации максимум не изменится, поскольку при замене дедлайна на меньший максимум увеличится, а новое значение [math]L_{k}[/math] будет равно [math]L_{i}[/math], либо мы не делали релаксацию, и значение [math]d_{k}[/math], и, следовательно, [math]L_{k}[/math] не поменяются.

Доказательство корректности

Первый шаг

Лемма:
Работа с новым сроком [math]{d'_i}[/math] в расписании не имеет опозданий тогда и только тогда, когда она не имела опозданий с оригинальным сроком [math]{d_i}[/math].
Доказательство:
[math]\triangleright[/math]

[math]\Rightarrow [/math]

Т.к. [math]{d'_i} \leqslant {d_i}[/math], значит, если опозданий не было со значениями [math]{d'_i}[/math], их не будет и со значениями [math]{d_i}[/math].

[math]\Leftarrow [/math]

Пусть у нас были сроки [math]{d_i}[/math] и мы их заменили на [math]{d'_i}[/math] в соответствии с приведенным алгоритмом.
Пронумеруем вершины от [math]1[/math] до [math]n[/math] в соответствии с обратным порядком обхода в алгоритме изменения сроков, причём [math]d_{i} \leqslant d_{j}[/math], если [math]i \leqslant j[/math]. В соответствии с расписанием, время, когда деталь закончит обрабатываться на станке [math]{C_i}[/math] удовлетворяет неравенству [math]{C_i} \leqslant {d_i}[/math] для всех [math]{C_1} \dots {C_n}[/math]. Тогда мы имеем [math]{C_n} \leqslant {d_n} = {d'_n}[/math]. Если для какого-то [math]1 \lt r \leqslant n[/math] мы имеем [math]{C_n} \leqslant {d'_n}[/math] для [math]i = r \dots n [/math] и существует работа [math]j[/math] из этого промежутка, что вершина с номером [math]r - 1[/math] является ее родителем, тогда [math]C_{r-1} \leqslant \min(d_{r-1},d'_{j}-1) = d'_{r-1}[/math].
[math]\triangleleft[/math]

Второй шаг

Расписание, сгенерированное этим алгоритмом имеет важное свойство: число заданий в очереди в любой момент времени [math]t[/math] меньше, чем в момент [math]t + 1[/math]. Действительно, пусть во время [math]t[/math] мы выполняем [math]k[/math] работ, и хотя бы [math]k + 1 \leqslant m[/math] работ готовы к выполению в момент времени [math]t + 1[/math]. Но т.к. [math]k + 1 \leqslant m[/math], значит каждой из работ предшествовала как минимум одна, поскольку у всех вершин, кроме корней, есть как минимум один предок. Значит, в момент времени [math]t[/math] исполнялось не менее [math]k + 1[/math] работ, противоречие.

Лемма:
Если существует такое расписание, в котором ни одна из работ не будет выполнена с опозданием, то тогда это свойство сохранится в построенном данным алгоритмом расписании
Доказательство:
[math]\triangleright[/math]

Предположим, что существует работа из [math]x_{1} \dots x_{n}[/math] расписания, построенного алгоритмом. В таком случае существует работа, которая опоздала по отношению к измененным срокам. Возьмем наименьшее [math]i[/math] такое, что [math]x(i) + 1 \gt d'_{i}[/math]. Пусть [math]t \lt d'_{i}[/math] — наибольшее из удовлетворяющих условию [math]j \lt m [/math], где [math] x(j) = t, d'_{j} \leqslant d'_{i}[/math] Такое [math]t[/math] существует, потому что иначе [math]m \cdot d'_{i}[/math] работ [math]j[/math] с [math]d'_{j} \leqslant d'_{i}[/math] находятся в очереди до [math]d'_{i}[/math]. Работа [math]i[/math] к ним не принадлежит, поскольку [math]x(i) + 1 \gt d'_{i}[/math], а значит, что [math]m \cdot d'_{i} + 1[/math] должны быть в очереди в момент времени [math]0 \dots d'_{i}[/math] и ни одна работа не должна опаздывать. Противоречие. Любая работа [math]j[/math] с [math]d'_{j} \leqslant d'_{i} [/math] и [math] x(j) \gt t [/math] должна иметь предка, начавшего работать в момент времени [math]t[/math]. Теперь рассмотрим два случая:

Первый случай: [math]t = d'_{i} - 1[/math].

Мы имеем [math]x(i)\gt d'_{i}-1 = t[/math]. Таким образом, предок [math]k[/math] работы [math]i[/math] должен начать работать во время [math]t[/math] и закончить в [math]d'_{i}[/math]. Но т.к. [math]d'_{k} \leqslant d'_{i} - 1 \lt d'_{i} = x(k) + 1[/math], работа [math]k[/math] так же опоздает, однако [math]i[/math] было выбрано минимальным. Противоречие.

Второй случай: [math]t \lt d'_{i} - 1[/math].

В этом случае [math]m[/math] работ [math]j[/math] таких, что [math]d'_{j} \leqslant d'_{i}[/math] начнут работать в момент времени [math]t + 1[/math], каждая из которых имеет как минимум работающего в [math]t[/math] предка. По структуре дерева все эти предки различны, кроме того, если [math]k[/math] — такой предок [math]j[/math], тогда [math]d'_{k} \leqslant d'_{j} - 1 \lt d'_{j} \leqslant d'_{i}[/math], что противоречит выбору [math]t[/math]
[math]\triangleleft[/math]

Корректность алгоритма

Теорема:
Данный алгоритм корректно решает задачу [math]P \mid intree, p_{i} = 1 \mid L_{max}[/math]
Доказательство:
[math]\triangleright[/math]
Пусть [math]L'_{max}[/math] — оптимальное значение. В таком случае, существует расписание, удовлетворяющее [math]\max\limits_i \{C_i - d_i\} \leqslant L'_{max}[/math], что эквивалетно выражению [math]C_{i} \leqslant d_{i} + L'_{max}[/math] для [math]i = 1 \dots n [/math]. По первой лемме расписание [math]S[/math], построенное для сдвинутых дат [math]d_{i} + L'_{max}[/math] удовлетворяет данным выражениям. Таким образом, оно оптимально. Нетрудно заметить, что [math]S[/math] идентично расписанию, построенному алгоритмом, т.к. [math](d_{i}+L'_{max})' = d'_{i} + L'_{max} [/math] для [math]i = 1 \dots n [/math]
[math]\triangleleft[/math]

Асимптотика

  1. На первом шаге мы посещаем каждую вершину не более двух раз (первый — когда ищем вершину без родителя, второй — когда релаксируем дедлайны) за [math]O(n)[/math] времени.
  2. Делаем сортировку вершин за [math]O(n \log n)[/math], а затем обходим все вершины по разу и считаем время начала выполнения каждой работы, в сумме за [math]O(n)[/math].

Итоговая сложность — [math]O(n \log n)[/math]

См. также

Источники информации

  • Peter Brucker «Scheduling Algorithms», fifth edition, Springer — с. 151-156 ISBN 978-3-540-69515-8