Рішення задач на графах

[ виправити ] текст може містити помилки, будь ласка перевіряйте перш ніж використовувати.

скачати

Міністерство освіти Республіки Білорусь
Установа освіти
«Гомельський державний університет ім. Ф. Скорини »
Математичний факультет
Кафедра МПУ
Курсова робота
Рішення задач на графах
Виконавець:
Студентка групи М-42
Макарченко А.Ю.
Науковий керівник:
олінскій М.С.
Гомель 2006

Введення
Існує цілий клас олімпіадних завдань з програмування, які простіше вирішуються, якщо учень володіє певним набором знань, умінь і навичок у галузі алгоритмів на графах. Це відбувається тому, що такі завдання можуть бути переформульовані в термінах теорії графів.
Теорія графів містить величезну кількість визначень, теорем і алгоритмів. І тому цей матеріал не може претендувати, і не претендує, на повноту охоплення матеріалу. Однак, на думку автора, пропоновані відомості є гарним Копроміс між обсягом матеріалу і його "коефіцієнтом корисної дії" в практичному програмуванні та вирішенні олімпіадних завдань.
Необхідно відзначити, що даний матеріал в істотному ступені спирається на наявність в учня певних навичок у використанні рекурсивних функцій та рекурентних співвідношень, які, зокрема, можуть з'явитися при роботі над авторськими матеріалами [9] та [10] відповідно.
Далі матеріал побудований таким чином. Наводиться мінімальна кількість базової теорії, після чого наводиться рішення великої кількості завдань з Білоруських республіканських і міжнародних (IOI - International Olympiad in Informatics) олімпіад з інформатики для школярів.
Автору видається найбільш ефективним наступний спосіб вивчення викладеного матеріалу. Після ознайомлення з загальними відомостями, розбір кожної нової задачі проводити в такому порядку. Прочитавши умови, відкласти матеріал у бік і спробувати вирішити завдання самостійно. Якщо ж Вам здасться, що Ви зайшли в глухий кут, і подальші роздуми не можуть привести до корисного результату, потрібно повертатися до матеріалу, прочитати повне рішення і самостійно реалізувати його на комп'ютері. Чим повніше і напруженіше буде проведена Вами робота над поточним завданням, тим більше шансів, що Ви вирішите наступне завдання самостійно. Більш того, така робота над кожною з наведених у цьому матеріалі завдань, призведе до значного збільшення шансів на те, що Ви вирішите нові завдання такого класу, які Вам обов'язково зустрінуться. І, навпаки, поверхневе читання може призвести, в кращому разі, до того, що Ви просто зрозумієте і, можливо, запам'ятайте рішення десятка наведених завдань. Це, звичайно, теж не мало, адже Вам можуть зустрітися ці, або схожі, задачі. Але автор ставить інше завдання - допомогти Вам навчитися вирішувати нові завдання такого класу.

1. Загальні відомості про алгоритми на графах

Насамперед кілька слів про те, як виникає поняття графа з природних умов завдань. Наведемо кілька прикладів.
Нехай ми маємо карту доріг, в якій для кожного міста вказано відстань до всіх сусідніх з ним. Тут два міста називаються сусідніми, якщо існує дорога, що сполучає безпосередньо ці два міста.
Аналогічно, можна розглянути вулиці і перехрестя всередині одного міста. Зауважимо, що можуть бути вулиці з одностороннім рухом.
Мережа комп'ютерів, з'єднаних провідними лініями зв'язку.
Набір слів, кожне з яких починається на певну букву і закінчується на цю ж або іншу літеру.
Безліч кісток доміно. Кожна кістка має 2 числа: ліву і праву половину кістки.
Пристрій, що складається з мікросхем, з'єднаних один з одним наборами провідників.
Генеалогічні дерева, що вказують родинні стосунки між людьми.
І, нарешті, власне графи, що вказують відносини між якими або абстрактними поняттями наприклад, числами.
Отже, неформально, граф можна визначити як набір вершин (міста, перехрестя, комп'ютери, літери, цифри кістки доміно, мікросхеми, люди) і зв'язків між ними: дороги між містами; вулиці між перехрестями; провідні лінії зв'язку між комп'ютерами; слова, що починаються на одну букву і закачується на іншу або цю ж літеру; провідники, що з'єднують мікросхеми; родинні стосунки, наприклад, Олексій - син Петра. Двонапрямлені зв'язку, наприклад, дороги з двостороннім рухом, прийнято називати ребрами графа; а односпрямовані зв'язку, наприклад, дороги з одностороннім рухом, прийнято називати дугами графа. Граф, у якому вершини з'єднуються ребрами, прийнято називати неорієнтованим графом, а граф, в якому хоча б деякі вершини з'єднуються дугами, прийнято називати орієнтованим графом.
Нижче наведено приклад неорієнтованого графа з шістьма вершинами.
(1) - (3) (5)
(2) - (4) (6)

2. Найкоротші відстані на графах

Графи можуть задаватися матрицею суміжності. Наприклад, для наведеного вище графа матриця суміжності виглядає наступним чином:
G 1 2 3 4 5 6
------------------
1 | 0 1 1 0 0 0
2 | 1 0 0 1 0 0
3 | 1 0 0 1 0 0
4 | 0 1 1 0 0 0
5 | 0 0 0 0 0 1
6 | 0 0 0 0 1 0
Тобто, G [i, j] = 1, якщо є дуга з вершини i у вершину j. і G [i, j] = 0 в іншому випадку.
Часто становить інтерес також і матриця досяжності графа, в якій G [i, j] = 1 якщо існує шлях по ребрах (дуг) вихідного графа з вершини i у вершину j
Наприклад, для графа, наведеного на прикладі вище, матриця досяжності буде мати вигляд:
G 1 2 3 4 5 6
------------------
1 | 1 1 1 1 0 0
2 | 1 1 1 1 0 0
3 | 1 1 1 1 0 0
4 | 1 1 1 1 0 0
5 | 0 0 0 0 1 1
6 | 0 0 0 0 1 1
Граф називається зваженим, якщо для його дуг вводяться ваги - наприклад довжини доріг між містами.
У зваженому графі G [i, j] - вага дуги від вершини i до вершини j.
На зважених графах можна вирішувати задачу знаходження найкоротшого відстані між вершинами.
Найпростіший для реалізації спосіб знаходження найкоротших відстаней від кожної вершини до кожної - це метод Флойда:
for k: = 1 to N do
for i: = 1 to N do
for j: = 1 to N do
G [i, j]: = min (G [i, j], G [i, k] + G [k, j]);
В результаті виконання цього алгоритму в G [i, j] сформується найкоротша відстань від вершини i до вершини j для всіх пар вершин i і j у графі.
Зауваження:
1. Початкові значення G [i, j] для вершин i і j, між якими у вихідному графі немає дуги, необхідно ініціалізувати завідомо надзвичайно великою величиною GREAT, наприклад, так
for i: = 1 to N do
for j: = 1 to N do G [i, j]: = GREAT;
де GREAT - підходяща за змістом константа.
Не рекомендується використовувати maxlongint в якості GREAT, оскільки в цьому випадку при складанні може виникнути переповнення.
2. Принагідне властивість алгоритму Флойда - можливість побудувати матрицю досяжності для вихідного графа, оскільки якщо G [i, j] залишилося рівним GREAT, це і означає, що вершина j недосяжна з вершини i.
3. Алгоритм Флойда працює також і в тому випадку, коли деякі з ребер вихідного графа мають негативні ваги (але гарантовано, що у графі відсутні цикли з негативним вагою)
У випадку, коли потрібно знаходити відстань між двома конкретними вершинами, або відстань від конкретної вершини до всіх інших, можна для прискорення роботи або скорочення використовуваної пам'яті використовувати декілька більш складний метод Дейкстра (див. завдання з пунктів 2.2., 2.3.).

2.1 Завдання "Маневри" (Автор - Перепечко С.М.)

Республіканська олімпіада з інформатики 2000р
На території деякої держави з сильно пересіченій, гірською місцевістю йдуть військові маневри між двома протиборчими сторонами - "Синіми" і "Зеленими". Особливості ландшафту та складні кліматичні умови змушують підрозділи обох сторін розміщуватися лише на території деяких населених пунктів. Загальна кількість населених пунктів у цій державі одно N.
Тактика ведення бойових дій "Синіх" розрахована на нанесення супротивнику швидких і раптових ударів, що можливо лише в тому випадку, якщо в операціях використовуються моторизовані частини, а їх пересування відбувається тільки по дорогах.
Різноманітність використовуваної бойової техніки призводить до того, що час переміщення різних бойових частин з одного пункту в інший виявляється різним і визначається величиною Vj - швидкістю руху підрозділів бойової частини, розквартированої в j-му населеному пункті.
Використовуючи величезну перевагу в техніці одна з сторін, під кодовою назвою "Сині", планує організувати нічний наліт на бази противника, під кодовою назвою "Зелені", і повністю їх розгромити. Усі бойові підрозділи "Синіх" приступають до виконання операції одночасно. Якщо бойова частина "Синіх" вривається у населений пункт, зайнятий "Зеленими", то, враховуючи фактор раптовості, їм вдається повністю розгромити це угруповання.
На жаль, блискучому проведення цієї операції завадило те обставина, що через час T після початку операції "Зеленими" був здійснений радіоперехоплення повідомлення про початок операції. Після радіоперехоплення угруповання "Зелених" миттєво розсіюються в навколишніх горах, і залишаються неушкодженими.
З'ясуйте яку кількість угруповань "Зелених" і в яких населених пунктах вдасться все-таки розгромити "Синім"?
Передбачається, що в початковий момент часу угруповання "Зелених" і "Синіх" не можуть знаходитися в одному і тому ж населеному пункті. Якщо сигнал тривоги надходить в той момент, коли бойова частина "Синіх" тільки вривається у населений пункт зайнятий "Зеленими", то використовуючи чудове знання місцевості, "Зеленим" все-таки вдається сховатися в горах. Переважна перевагу в техніці і живій силі дозволяє бойовим частинам "Синіх" організувати з кожної частини будь-яку кількість експедицій для розгрому "Зелених". Ніщо не заважає однієї експедиції за час проведення операції знищити кілька угруповань.
Опис формату вхідних даних
Вихідні дані задачі містяться у файлах MAP.IN і TROOPS.IN Структура файлу MAP.IN описує карту місцевості. У першому рядку цього файлу містяться два цілих числа: N - кількість населених пунктів (0 <N <256) і K - кількість доріг, що з'єднують ці населені пункти (0 <= K <= 1000). Дороги ніде не перетинаються. У наступних K рядках файлу міститься схема доріг. У кожному рядку записана пари двох натуральних чисел i та j і одне позитивне дійсне число Lij, що означає, що між населеними пунктами i та j існує дорога довжиною Lij кілометрів (Lij <1000).
Вміст файлу TROOPS.IN відображає розміщення бойових частин воюючих сторін. Перший рядок файлу містить число MF - кількість бойових частин "Синіх". Кожен з наступних MF рядків містять по два числа. Перше - ціле число j - номер населеного пункту, в якому розміщується частина, друге - речовий невід'ємне число Vj - швидкість руху бойових колон цій частині в км / год (Vj <110). Далі в окремому рядку файлу записано число MB - кількість бойових угруповань "Зелених", за яким перераховані MB чисел - номери населених пунктів, в яких ці угруповання перебувають. І, нарешті, в останньому рядку файлу зберігається позитивне дійсне число T, виміряний в годинах (T <24). Всі числа в рядках файлів розділені пробілами.
Опис формату вихідних даних
Результат розв'язання задачі необхідно вивести в текстовий файл VICTORY.OUT. У перший рядок файлу виводиться кількість розгромлених угруповань, а в другу - у порядку зростання номера населених пунктів, в яких ці угруповання базувалися.
Приклад вхідних та вихідних даних
MAP.IN
7 серпня 1 C
1 лютого 1980 |
2 квітень 1925 80 |
5 квітня 1910 | 5 6 10 7 15 8
2 червня 2 травня o --- З ---- o ---- З
2 березня 1940 / \
7 Червня 1910 25 / \ 40
7 серпня 1915 / \
TROOPS.IN 4 З 3 З
2 |
Січень 1950 10 |
20 червня |
4 4 5 3 8 5 З
2.0
VICTORY.OUT
2
4 серпня
Коротко алгоритм розв'язання задачі може бути описаний ІАК:
Методом Флойда обчислюємо найкоротші відстані від кожної вершини до кожної. Циклом по всіх вершин "Зелених", циклом по всім вершин "Синіх", якщо "Сині" встигають, значить відповідна угруповання "Зелених" розбита.
Розглянемо рішення задачі докладніше:
Спочатку йде введення вихідних даних:
read (N, K); {введення кількості пунктів і доріг}
for x: = 1 to N do {ініціалізація доріг для методу Флойда}
for y: = 1 to N do a [x, y]: = 1000000000000.0;
for i: = 1 to K do
begin
read (x, y, z);
a [x, y]: = z; a [y, x]: = z; {коригування матриці суміжності}
end;
read (NumBlue); {введення розташування і швидкостей "Синіх"}
for i: = 1 to NumBlue do read (Blue [i], VBlue [i]);
read (NumGreen); {введення розташування "Зелених"}
for i: = 1 to NumGreen do read (Green [i]);
read (T); {ввести час проведення операції}
Потім - розрахунок відстаней між усіма населеними пунктами методом Флойда:
for i: = 1 to N do
for x: = 1 to N do
for y: = 1 to N do
a [x, y]: = min (a [x, y], a [x, i] + a [i, y]);
Потім розрахунок кількості знищених угруповань
Deleted: = 0;
for i: = 1 to NumGreen do {Для всіх "Зелених"}
for j: = 1 to NumBlue do {Для всіх "Синіх"}
if a [Green [i], Blue [j]] <Vblue [j] * T {Якщо "Сині" встигнуть}
then
begin
inc (Deleted); {знищених інкрементіруем}
Num [Deleted]: = Green [i]; {Номер заносимо в Num}
break; {Переходимо до наступних "Зеленим"}
end;
Нарешті, у відповідності з вимогами завдання сортуємо номери знищених угруповань (в масиві Num) і виводимо їх.
Sort;
writeln (Deleted);
for I: = 1 to Deleted do write (Num [i], '');

Нижче наводиться повний текст рішення задачі.
Розмірність вихідного графа зменшена до 100 * 100, оскільки 255 * 255 дійсних чисел не поміщається в статичній пам'яті Турбо-Паскаля. (Дивись зауваження у пункті 6).
{$ R +, E +, N +}
program by00d1m3;
var
a: array [1 .. 100,1 .. 100] of single;
blue, green, Num: array [1 .. 100] of longint;
Vblue: array [1 .. 100] of real;
i, j, K, N, x, y, NumBlue, NumGreen, Deleted: longint;
z, T: real;
function min (a, b: real): real;
begin
if a <b then min: = a else min: = b
end;
procedure Sort;
begin
for i: = 1 to Deleted-1 do
begin
x: = Num [i]; y: = i;
for j: = i +1 to Deleted do
if num [j] <x
then begin x: = Num [j]; y: = j; end;
Num [y]: = Num [i]; Num [i]: = x;
end;
end;
begin
assign (input, 'map.in'); reset (input);
assign (output, 'victory.out'); rewrite (output);
read (N, K);
for x: = 1 to N do
for y: = 1 to N do a [x, y]: = 1000000000000.0;
for i: = 1 to K do
begin
read (x, y, z);
a [x, y]: = z; a [y, x]: = z;
end;
read (NumBlue);
for i: = 1 to NumBlue do read (Blue [i], VBlue [i]);
read (NumGreen);
for i: = 1 to NumGreen do read (Green [i]);
read (T);
for i: = 1 to N do
for x: = 1 to N do
for y: = 1 to N do
a [x, y]: = min (a [x, y], a [x, i] + a [i, y]);
Deleted: = 0;
for i: = 1 to NumGreen do
for j: = 1 to NumBlue do
if a [Green [i], Blue [j]] <= Vblue [j] * T +0.00001
then
begin
inc (Deleted);
Num [Deleted]: = Green [i];
break;
end;
Sort;
writeln (Deleted);
for I: = 1 to Deleted do write (Num [i], '');
close (input); close (output);
end.

2.2 Завдання "Піраміда Хеопса" (Автор - Котов В.М.)

Республіканська олімпіада з інформатики 1996
Всередині піраміди Хеопса є N кімнат, в яких встановлені 2М модулів, що складають М пристроїв кожен пристрій складається з двох модулів, які розташовуються в різних кімнатах, і призначене для переміщення між парою кімнат, в яких встановлені ці модулі. Переміщення відбувається за 0.5 умовних одиниці часу. У початковий момент часу модулі всіх пристроїв переходять в "підготовчий режим". Кожен з модулів має деякий свій цілочисельний період часу, протягом якого він перебуває у "підготовчому режимі". Після закінчення цього часу модуль мгоновенно "спрацьовує" після чого знову переходить в "підготовчий режим". Пристроєм можна скористатися тільки в той момент, коли одночасно "спрацьовують" обидва його модуля.
Індіана Джонс зумів проникнути в гробницю фараона (кімната 1). Обстеживши її, він включив пристрою і зібрався йти, але в цей момент прокинувся зберігач гробниці. Тепер Джонсу необхідно якомога швидше потрапити в кімнату N, в якій знаходиться вихід з піраміди. При цьому з кімнати в кімнату він може потрапляти тільки за допомогою пристроїв, так як прокинувся зберігач закрив всі двері в кімнатах піраміди.
Необхідно написати програму, яка отримує на вході опису розташування пристроїв та їх характеристик (дивися опис формату вводу), а видає значення оптимального часу і послідовність пристроїв, якими треба скористатися, щоб потрапити з кімнати 1 в кімнату N за цей час.
Вхідний файл: input2.txt Вихідний файл: output2.txt
Формат вводу:
N
M
R11 T11 R12 T12
RM1 TM1 RM2 TM2
де N (2 <= N <= 100) - кількість кімнат
M (M <= 100) - кількість пристроїв
Ri1 і Ri2 - номери кімнат, у яких розташовуються модулі пристрою i Ti1, Ti2 (Ti1, Ti2 <= 1000) - періоди часу, через які спрацьовують ці модулі Всі числа - натуральні.
Приклад
4
5
1 5 3 2
1 1 2 1
2 5 3 5
4 4 3 2
3 5 4 5
Оптимальний час: 8.5 шукана послідовність: 2 3 4
Коротко алгоритм рішення може бути викладений таким чином.
Представивши кімнати - вершинами, а пари пристроїв (з часом спрацьовування) - виваженими дугами, можемо застосувати алгоритм Дейкстра для пошуку найкоротшого шляху від першої вершини до останньої. Точніше, алгоритм Дейкстра побудує найкоротші шляхи від першої до всіх інших, але в даній задачі нас цікавить тільки найкоротший шлях від першої до останньої. Потрібно враховувати, що дуги існують тільки в моменти, кратні часів спрацьовування.
Розглянемо рішення більш докладно.
Введення вихідних даних:
read (N, M); for I: = 1 to N do kG [i]: = 0;
for i: = 1 to M do
begin
read (r1, t1, r2, t2);
nok: = (t1 * t2) div Nod (t1, t2); {nok - найменше спільне кратне}
{Nod-найбільший загальний дільник}
{Чисел t1 і t2}
inc (kG [r1]); G [r1, kg [r1]]: = r2; P [r1, kg [r1]]: = nok;
inc (kG [r2]); G [r2, kg [r2]]: = r1; P [r2, kg [r2]]: = nok;
end;
Вихідний граф за умовами задачі неорієнтований. Тому ми вводимо обидві дуги. При цьому граф представляється у вигляді списку дуг суміжних з поточної:
kG [i] - кількість дуг з вершини i
G [i, j] - вершина, до якої з вершини i йде дуга під порядковим номером j
P [i, j] - вага дуги з вершини i вершину G [i, j]
Для обчислення nod використовується функція, заснована на алгоритмі Евкліда:
function Nod (a, b: longint): longint;
begin
while (a <> b) do if a> b then a: = ab else b: = ba;
Nod: = a;
end;
Потім проводиться підготовка до виконання алгоритму Дейкстри:
for i: = 1 to N do {Для всіх вершин}
begin
Labl [i]: = FREE; {Позначаємо як вільну}
divd [i]: = 1; {Предок - перша}
d [i]: = maxlongint; {Відстань до першої - маx}
end;
Labl [1]: = DONE; {Перша - оброблена}
Pred [1]: = 0; {Предок у першій - 0}
d [1]: = 0; {Відстань від першої до першої - 0}
for j: = 1 to kG [1] do {Для всіх дуг із першого ребра}
d [G [1, j]]: = P [1, j] +0.5; {Відстань до першої вершини}
{Дорівнює вазі ребра + 0.5}
Зауважимо, що в останньому рядку до ваги ребер додається 0.5, оскільки за умовами завдання переміщення за допомогою пристроїв з однієї кімнати в іншу займає 0.5 одиниці часу.
Далі йде основний цикл алгоритму Дейкстра
for i: = 1 to N-1 do
begin
Пошук найближчої до першої вершини з необроблених
Перерахунок найкоротших відстаней через найближчу вершину до вершин, суміжних з найближчою
end;

Перший етап - "Пошук найближчої до першої вершини з необроблених"
MinD: = maxlongint; {MinD - max}
for j: = 1 to N do {Для всіх вершин}
if (Labl [j] = FREE) {якщо вершина вільна}
and (d [j] <MinD) {і відстань від неї до першої}
{Вершини менше MinD}
then
begin
MinD: = d [j]; {Заносимо це відстань у MinD}
Bliz: = j {Вершину запам'ятовуємо як найближчу}
end;
Labl [Bliz]: = DONE; {Знайдену найближчу позначаємо}
{Як оброблену}
Другий етап - "Перерахунок найкоротших відстаней через найближчу вершину до вершин, суміжних з найближчої"
for j: = 1 to kG [Bliz] do {Для всіх вершин суміжних з найближчою}
if (Labl [G [Bliz, j]] = FREE) {Якщо вершина ще не оброблялася}
then
begin
NewTime: = Calculat (d [bliz], P [bliz, j]); {Вичіслітьрасстояніедонее}
{В термінах задачі-час}
{Перемісила в неї}
if d [G [Bliz, j]]> NewTime +0.5 {Якщо новий час краще}
then
begin
d [G [Bliz, j]]: = NewTime +0.5; {заносимо його в масив D}
divd [G [Bliz, j]]: = Bliz; {вказуємо найближчу як}
end; {попередню для поточної}
end;
При обчисленні найкоротшої відстані до поточної вершини через найближчу (в термінах алгоритму Дейкстра) або часу переміщення з першої кімнати через бліжашую в поточну (в термінах даної задачі) використовується функція Calculat (T, Tnok):
function Calculat (T: single; Tnok: longint): longint;
var i: longint;
begin
TRes: = 0;
while (T> TRes) do inc (TRes, Tnok);
Calculat: = TRes;
end;
Ця функція обчислює час TRes (яке передається в зухвалу програму безпосередньо через ім'я функції Calculat), найближче більше поточного часу T (T одно мінімального значення часу, який знадобився, що б оптимальним чином дістатися з першої вершини до найближчої вершини) і кратне Tnok - періоду спрацьовування пари пристроїв, що пов'язують кімнати Bliz (найближча) і G [Bliz, j] (поточна).
Після виконання алгоритму Дейкстра сформовані масиви
d [j] - найкоротша відстань від початкової вершини до вершини j
divd [j] - предок вершини j на оптимальний маршрут
За цією інформацією висновок результату здійснюється наступним чином:
tg: = N; i: = 0; {Починаючи з останньої вершини}
while tg <> 0 do {Поки не закінчилися попередні}
begin
inc (i); {інкрементіруем кількість вершин в дорозі}
path [i]: = tg; {Заносимо поточну в масив шляху}
tg: = divd [tg]; {Переустановлювати попередню}
end;
writeln ('Оптимальний час:', D [N]: 0:1);
write ('шукана послідовність:');
for j: = i-1 downto 1 do write ('', path [j]);
Нижче наведено повний текст рішення задачі.
{$ R +, E +, N +}
program by96d1s2;
const
FREE = 1;
DONE = 2;
var
G: array [1 .. 100,1 .. 100] of byte;
P: array [1 .. 100,1 .. 100] of longint;
D: array [1 .. 100] of single;
Labl, divd, kG: array [1 .. 100] of byte;
path: array [1 .. 100] of byte;
is, t1, t2, nok, Tres, NewTime: longint;
i, j, x, y, A, B, C, N, M, tg, Bliz: byte;
r1, r2: byte;
Yes: boolean;
MinD: single;
function Nod (a, b: longint): longint;
begin
while (a <> b) do
if a> b then a: = ab else b: = ba;
Nod: = a;
end;
function Calculat (T: single; Tnok: longint): longint;
var i: longint;
begin
TRes: = 0;
while (T> TRes) do inc (Tres, Tnok);
Calculat: = TRes;
end;
begin
assign (input, 'input2.txt'); reset (input);
assign (output, 'output2.txt'); rewrite (output);
read (N, M);
for I: = 1 to N do kG [i]: = 0;
for i: = 1 to M do
begin
read (r1, t1, r2, t2); nok: = (t1 * t2) div Nod (t1, t2);
inc (kG [r1]); G [r1, kg [r1]]: = r2; P [r1, kg [r1]]: = nok;
inc (kG [r2]); G [r2, kg [r2]]: = r1; P [r2, kg [r2]]: = nok;
end;
for i: = 1 to N do
begin
Labl [i]: = FREE; divd [i]: = 1; d [i]: = maxlongint;
end;
Labl [1]: = DONE; Pred [1]: = 0; d [1]: = 0;
for j: = 1 to kG [1] do d [G [1, j]]: = P [1, j] +0.5;
for i: = 1 to N-1 do
begin
MinD: = maxlongint;
for j: = 1 to N do
if (Labl [j] = FREE) and (d [j] <MinD)
then begin MinD: = d [j]; Bliz: = j end;
Labl [Bliz]: = DONE;
for j: = 1 to kG [Bliz] do
if (Labl [G [Bliz, j]] = FREE)
then begin
NewTime: = Calculat (d [bliz], P [bliz, j]);
if d [G [Bliz, j]]> NewTime +0.5
then
begin
d [G [Bliz, j]]: = NewTime +0.5;
divd [G [Bliz, j]]: = Bliz;
end;
end;
end;
tg: = N; i: = 0;
while tg <> 0 do
begin
inc (i); path [i]: = tg; tg: = divd [tg];
end;
writeln ('Оптимальний час:', D [N]: 0:1);
write ('шукана послідовність:');
for j: = i-1 downto 1 do write ('', path [j]);
close (input); close (output);
nd.

2.3 Завдання "Ех, дороги" (Автор - Котов В.М.)

Республіканська олімпіада з інформатики 1998
Є N міст, які пронумеровані від 1 до N (де N - натуральне, 1 <N <= 100). Деякі з них з'єднані двосторонніми дорогами, які перетинаються лише в містах. Є два типи доріг - шосейні і залізні. Для кожної дороги відома базова вартість проїзду по ній.
Необхідно проїхати з міста А в місто В, сплативши мінімальну суму за проїзд. Вартість проїзду залежить від набору проїжджаємо доріг і від способу проїзду. Так, якщо ви під'їхали до міста З по шосейній (залізної) дорозі X-> C і хочете їхати далі по дорозі C-> Y того ж типу, то ви повинні сплатити тільки базову вартість проїзду по дорозі C-> Y. Якщо тип дороги C-> Y різниться від типу дороги Х-> C, то ви повинні сплатити базову вартість проїзду по дорозі C-> Y плюс 10% від базової вартості проїзду по цій дорозі (страховий внесок). При виїзді з міста А страховий внесок сплачується завжди. Написати програму, яка знаходить найдешевший маршрут проїзду у вигляді послідовності міст і обчислює вартість проїзду за цим маршрутом.
Специфікація вхідних даних
Вхідні дані знаходяться у текстовому файлі з ім'ям TOUR.IN і мають наступний формат:
- У першому рядку знаходяться число N
- У другому рядку - число M (кількість доріг, натуральне M <= 1000);
- В кожній з наступних M рядків знаходяться 4 числа x, y, t, p, розділені пробілом, де x і y - номери міст, які з'єднує дорога, t - тип дороги (0 - шосейна, 1 - залізна), p - базова вартість проїзду по ній (p - дійсне, 0 <p <= 1000).
- В останньому рядку задається номери початкового і кінцевого міст A і B.
Специфікація вихідних даних
Вихідні дані повинні бути записані в текстовий файл з ім'ям TOUR.OUT і мати наступний формат:
- У першому рядку знаходиться число S - вартість проїзду за найдешевшим маршрутом, з точністю 2 знаки після коми;
- В кожній з наступних рядків, крім останньої, знаходиться два числа - номер чергового міста в маршруті (починаючи з міста А) і тип дороги, по якій він виїжджає з цього міста (0 або 1), розділені пропуском.
- В останньому рядку знаходиться єдине число - номер міста B.
Приклад вхідного файлу Приклад вихідного файлу
TOUR.IN TOUR.OUT
3 22.0
2 1 0
1 2 0 10.00 2 1
2 3 1 10.00 3
3 січня
Метод Дейкстра безпосередньо (міста - вершини) не може бути застосований, оскільки оптимальний маршрут може зажадати заїзду в один і той же місто два рази з метою менше платити за страховий збір на виїзд з цього міста.
Вводимо 2 * N вершин (де N - число міст). Для кожного міста - дві вершини. Перша, якщо ми в'їхали в місто по дорозі типу 0, друга - якщо в'їхали в місто по дорозі типу 1.
Тепер можна застосовувати безпосередньо метод Дейкстра для обчислення найкоротших відстаней від заданого міста A до всіх введених вершин.
Для кінцевого міста B ми вибираємо найкращий з варіантів - заїхати в B по дорозі типу 0 або по дорозі типу 1.
Метод Дейкстра дозволяє зберігати попередників вершин на оптимальний маршрут, що й використовується для виведення повної відповіді в заданому форматі.
Розглянемо рішення задачі більш докладно, грунтуючись на тексті програми - рішення:
Введення вихідних даних здійснюється наступним чином:
read (N); {N - число міст}
read (M); {M - число доріг}
for x: = 1 to N do {Встановлюємо}
for y: = 1 to N do {між усіма містами}
for t: = 0 to 1 do p [x, y, t]: = MAXR; {максимальну вартість}
for i: = 1 to M do {x - номер міста "звідки"}
begin {y - номер міста "куди"}
readln (x, y, t, p [x, y, t]); {t - тип дороги}
p [y, x, t]: = p [x, y, t]; {P [x, y, t] - вартість}
end; {проїзду від x до y по дорозі типу t}
read (A, B); {Початковий і кінцевий пункти маршруту}
Підготовка до виконання алгоріма Дейкстра:
for i: = 1 to N do {При виїзді з міста}
for t: = 0 to 1 do {страховий внесок}
p [a, i, t]: = 1.1 * p [a, i, t]; {сплачується завжди}
for i: = 1 to N do {Для всіх міст}
begin {найкоротші вартості до першого}
D [i, 0]: = P [a, i, 0]; {по шосейній дорозі}
D [i, 1]: = P [a, i, 1]; {по залізниці}
end;
for i: = 1 to N do {Для всіх міст}
begin
Labl [i, 0]: = FREE; {Вільні}
Labl [i, 1]: = FREE; {по обом типам доріг}
divd [i, 0]: = A; {Попередній місто дорівнює A}
divd [i, 1]: = A; {для обох типів доріг}
end;
Labl [A, 0]: = DONE; {Початкове місто оброблений}
abl [A, 1]: = DONE; {для обох типів доріг}
divd [A, 0]: = 0; {Попередній у початкового-0}
divd [A, 1]: = 0; {для обох типів доріг}
Основна частина алгоритму Дейкстра являє собою цикл за кількістю вершин залишилися необробленими
for i: = 1 to 2 * N-2 do
begin
Пошук найближчої до першої вершини з необроблених
Перерахунок найменших вартостей через найближчу вершину до вершин, суміжних з найближчою end;
Перший етап - "Пошук найближчої до першої вершини з необроблених"
MinD: = MaxR; {MinD - максимум}
for J: = 1 to N do {Для всіх міст}
for t: = 0 to 1 do {Для доріг обох типів}
if (Labl [j, t] = FREE) {Якщо в'їзд в місто j по дорозі типу t}
and {ще не оброблявся і}
(MinD> d [j, t]) {вартість від міста A до міста j}
then {по дорозі типу t менше ніж MinD, то}
begin
Bliz: = j; {Бліжайшаяя - j}
bt: = t; {її тип - bt}
MinD: = d [j, t] {мінімальну вартість у MinD}
end;
Labl [Bliz, bt]: = DONE; {Позначити як оброблену вершину}
{З мінімальною вартістю від міста A}
{З ще необроблених вершин}
Другий етап - "Перерахунок найменших вартостей через найближчу вершину до вершин, суміжних з найближчої"
for j: = 1 to N do {Для всіх міст}
for t1: = 0 to 1 do {Для доріг обох типів}
if (Labl [j, t1] = FREE) {Якщо дорога до міста j типу t необроблений}
and (d [j, t1] {і вартість до неї від вершини A}
> {Більше ніж}
d [Bliz, bt] + C (t1, bt) * P [Bliz, j, t1]) {вартість через найближчу}
then {то}
begin
d [j, t1]: = d [Bliz, bt] + C (t1, bt) * P [Bliz, j, t1]; {замінюємо вартість}
Pred [j, t1]: = Bliz; Typp [j, t1]: = bt; {запам'ятовуємо}
end; {попередні місто і тип дороги}
При обчисленні вартості проїзду через найближчу вершину враховується страховий збір при переході з дороги одного типу на дорогу іншого типу з допомогою функції C (t1, t2):

function C (t1, t2: byte): real;
begin
if t1 = t2 then C: = 1.0 else C: = 1.1
end;
Висновок результатів здійснюється так:
G: = B; is: = 0; {Поточний місто - кінцевий місто}
if d [B, 0] <d [B, 1] {Тип в'їзду -}
then t: = 0 else t: = 1; {кращий з двох можливих}
stt [1]: = t; {Поточний тип - обраний кращий}
while (G <> 0) do {Поки поточний місто не дорівнює 0}
begin
inc (is); {інкрементіруем кількість міст}
stg [is]: = divd [G, t]; {зберігаємо поточний місто в дорогу}
stt [is +1]: = typp [G, t]; {і його тип}
NG: = divd [G, t]; {Переустановлювати поточні місто}
NT: = typp [G, T]; {і тип на попередні місто}
G: = NG; t: = NT; {і тип}
end;
writeln (min (d [B, 0], d [B, 1]): 0:2); {виводимо мінімальну вартість}
for i: = is-1 downto 1 do writeln (stg [i], '', stt [i]); {і шлях}
writeln (B); {додаємо в дорогу останнє місто}
Далі наводиться повний текст рішення задачі.
{$ R +, E +, N +}
program by98d1m2;
const
FREE = 0;
DONE = 1;
MAXR = 1e4;
var
p: array [1 .. 80,1 .. 80,0 .. 1] of single;
D: array [1 .. 80,0 .. 1] of single;
divd: array [1 .. 80,0 .. 1] of 0 .. 100;
typp, Labl: array [1 .. 80,0 .. 1] of 0 .. 1;
stg: array [1 .. 100] of 0 .. 100;
stt: array [1 .. 100] of 0 .. 1;
M: 0 .. 1000;
N, x, y, A, B, i, k, j, G, NG: 0 .. 100;
t, t1, t2, bt, NT: 0 .. 1;
is, Bliz: 0 .. 100;
Mind: single;
Mint, MT: byte;
function C (t1, t2: byte): real;
begin
if t1 = t2 then C: = 1.0 else C: = 1.1
end;
function min (x, y: single): single;
begin
if x <= y then min: = x else min: = y
end;
begin
assign (input, 'tour.in'); reset (input);
assign (output, 'tour.out'); rewrite (output);
read (N);
read (M);
for x: = 1 to N do
for y: = 1 to N do
for t: = 0 to 1 do p [x, y, t]: = MAXR;
for i: = 1 to M do
begin
readln (x, y, t, p [x, y, t]); p [y, x, t]: = p [x, y, t];
end;
read (A, B);
for i: = 1 to N do
for t: = 0 to 1 do p [a, i, t]: = 1.1 * p [a, i, t];
for i: = 1 to N do
begin D [i, 0]: = P [a, i, 0]; D [i, 1]: = P [a, i, 1]; end;
for i: = 1 to N do
begin
Labl [i, 0]: = FREE; Labl [i, 1]: = FREE;
divd [i, 0]: = A; divd [i, 1]: = A;
end;
Labl [A, 0]: = DONE; Labl [A, 1]: = DONE; divd [A, 0]: = 0; Pred [A, 1]: = 0;
for i: = 1 to 2 * N-2 do
begin
MinD: = MaxR;
for J: = 1 to N do
for t: = 0 to 1 do
if (Labl [j, t] = FREE) and (minD> d [j, t])
then begin
Bliz: = j; bt: = t; MinD: = d [j, t]
end;
Labl [Bliz, bt]: = DONE;
for j: = 1 to N do
for t1: = 0 to 1 do
if (Labl [j, t1] = FREE) and
(D [j, t1]> d [Bliz, bt] + C (t1, bt) * P [Bliz, j, t1])
then begin
d [j, t1]: = d [Bliz, bt] + C (t1, bt) * P [Bliz, j, t1];
Pred [j, t1]: = Bliz; Typp [j, t1]: = bt;
end;
end;
G: = B; is: = 0;
if d [B, 0] <d [B, 1] then t: = 0 else t: = 1;
stt [1]: = t;
while (G <> 0) do
begin
inc (is);
stg [is]: = divd [G, t]; stt [is +1]: = typp [G, t];
NG: = divd [G, t]; NT: = typp [G, T];
G: = NG; t: = NT;
end;
writeln (min (d [B, 0], d [B, 1]): 0:2);
for i: = is-1 downto 1 do writeln (stg [i], '', stt [i]);
writeln (B);
close (input); close (output);
end.

3. Пошук в глибину

Нижче наведено приклад неорієнтованого графа з шістьма вершинами.
(1) - (3) (5)
I / II
I / \ II
(2) - (4) (6)
При комп'ютерній обробці граф може задаватися списком ребер (дуг) для кожної вершини. Наприклад, для графа, наведеного на прикладі, цей список виглядає так
1: 2,3,4 (перша вершина з'єднана з другої, третьої і четвертої)
2: 1,3,4
3: 2,3,4
4: 1,2,3
5: 6
6: 5
Для зберігання цієї інформації в пам'яті комп'ютера можна скористатися двовимірним масивом G [1 .. N, 1 .. M], де N - кількість вершин у графі, M - максимально можливе число ребер (дуг) в однієї вершини графа. Для зручності обробки цієї інформації можна також мати одновимірний масив kG [1 .. N] - кількості ребер (дуг) вершин графа.
Тоді для обробки списку зв'язків поточної вершини U можна написати
for i: = 1 to kG [U]
begin
V: = G [U, i];
end;
Тим самим, ми отримуємо обробку дуги, яка зв'язує вершини U і V для всіх вершин, безпосередньо пов'язаних з U.
Для обробки ВСІХ зв'язків усіх вершин можна використовувати пошук в глибину (DFS - Depth-First Search):
for U: = 1 to N do Color [U]: = WHITE;
for U: = 1 to N do
if color [U] = WHITE then DFS (U);
Procedure DFS (U: longint);
var
j: longint;
begin
color [U]: = GRAY;
for j: = 1 to kG [U] do DFS (G [U, j]);
end;
Тут
Color [U] = колір вершини
WHITE (константа = 1) - білий,
якщо ми ще не відвідували цю вершину
GRAY (константа = 2) - сірий,
якщо ми відвідували дану вершину
DFS (U) - рекурсивна процедура, яка викликає себе
для всіх вершин, нащадків даної вершини.
Тобто, для забезпечення пошуку в глибину на заданому графі G [1 .. N, 1 .. M] з кількостями дуг з вершин G [1 .. N], спочатку ми встановлюємо всім вершин колір WHITE, а потім для всіх вершин, які ще не відвідувалися (Color [G [U, j]] = WHITE) викликаємо рекурсивну процедуру DFS.
Таким способом формуються всі можливі маршрути в графе.Прі цьому немає обмежень на кількість разів використання дуг і заходів у вершини.
Якщо ж за умовами завдання буде потрібно відвідувати кожну вершину не більше одного разу, щоб формувати маршрути з незбіжних вершин, то це може бути забезпечено в процедурі DFS умовним викликом:
Procedure DFS (U: longint);
var
j: longint;
begin
color [U]: = GRAY;
for j: = 1 to kG [U] do
if color [G [U, j]] = WHITE then DFS (G [U, j]);
end;
Якщо за умовами задачі потрібно кожну дугу використовувати не більше одного разу, то можна ввести забарвлення дуг:
Color [1 .. N, 1 .. M] - зі значеннями FREE або DONE
де, як і раніше
N - кількість вершин у графі,
M - максимально можливе число ребер (дуг) в однієї вершини графа.
А процедура DFS формування маршрутів так, що б кожна дуга використовувалася в маршруті не більше одного разу, буде виглядати наступним чином:

procedure DFS (v: byte);
var j: byte;
begin
for j: = 1 to kG [v] do
if (color [v, j] = FREE)
then
begin
color [v, j]: = DONE;
DFS (G [v, j]);
color [v, j]: = FREE;
end;
end;
Тут вводяться позначки на дуги Color [v, j] = FREE, якщо дуга ще не оброблена, і DONE, якщо вона включена в поточний шлях.
Якщо в задачі потрібно вивести знайдений шлях, то для його зберігання заводиться спеціальний масив SLED [1 .. Q], де Q - максимальна кількість ребер у дорозі і вершини поточного шляху заносяться в цей масив і видаляються з нього в процесі обходу графа:
procedure DFS (v: byte);
var j: byte;
begin
for j: = 1 to kG [v] do
begin
inc (ks); sled [ks]: = G [v, j];
DFS (G [v, j]);
dec (ks);
...
end;
end;
Наведена теоретична інформація ілюструється далі прикладами рішення конкретних завдань.

3.1 Завдання "Дороги"

Республіканська олімпіада з інформатики 1997
Дана система односторонніх доріг, обумовлена ​​набором пар міст. Кожна така пара (i, j) вказує, що з міста i можна проїхати в місто j, але це не означає, що можна проїхати в зворотному напрямку.
Необхідно визначити, чи можна проїхати з заданого міста А в заданий місто У таким чином, щоб відвідати місто С і не проїжджати ні по якій дорозі більше одного разу.
Вхідні дані задаються у файлі з ім'ям PATH.IN наступним чином. У першому рядку знаходиться натуральне N (N <= 50) - кількість міст (міста нумеруються від 1 до N). У другому рядку знаходиться натуральне M (M <= 100) - кількість доріг. Далі в кожному рядку знаходиться пара номерів міст, які пов'язує дорога. В останній (M +3)-му рядку знаходяться номери міст А, В і С.
Відповіддю є послідовність міст, що починається містом А і закінчується містом В, що задовольняє умовам задачі, який повинен бути записаний у файл PATH.OUT у вигляді послідовності номерів міст по одному номеру в рядку. Перший рядок файлу повинна містити кількість міст в послідовності. При відсутності шляху записати в перший рядок файлу число -1.
Приклад Відповідь
3 березня
2 Січень
1 2 2
2 3 3
1 3 2
Коротко ідея рішення може бути викладена наступним чином:
Пошук в глибину.
Якщо зустрічаємо вершину B, встановлюємо відповідний прапор.
Якщо зустрічаємо вершину C і прапор B встановлено - виводимо результат і завершуємо роботу.
Після завершення пошуку (потрібного маршрут не знайдено) виводимо -1.
Викладемо рішення більш докладно.
Введення вихідних даних здійснюється наступним чином:
read (N, M);
for i: = 1 to M do
begin
read (x, y); inc (kG [x]); G [x, kG [x]]: = y; color [x, kG [x]]: = FREE;
end;
read (A, B, C);
Тут, як і колись,
kG [i] - кількість дуг з вершини i
G [i, j] - (для j від 1 до kG [j]) - вершини, з якими вершина i пов'язана відповідної дугою /
Крім того, введений колір дуги
FREE - вільна (DONE - зайнята, FREE / DONE - константи)
Головна програма фактично включає тільки наступні оператори:
LabC: = 0; {Встановити мітку - вершину C ще не відвідували}
DFS (A); {Пошук в глибину від вершини A}
writeln (-1); {Висновок ознаки відсутності необхідного шляху}
Рекурсивна процедура пошуку в глибину від вершини V виглядає наступним чином:
procedure DFS (v: byte);
var j: byte;
begin
for j: = 1 to kG [v] do
if (color [v, j] = FREE)
then
begin
if (G [v, j] = B) and (LabC = 1)
then begin OutRes; halt; end;
if G [v, j] = C then LabC: = 1;
color [v, j]: = DONE; inc (ks); sled [ks]: = G [v, j];
DFS (G [v, j]);
color [v, j]: = FREE; sled [ks]: = 0; dec (ks);
if G [v, j] = C then LabC: = 0;
end;
end;
Тобто для всіх ще необроблених (color [v, j] = FREE) дуг від поточної вершини з'ясовуємо - якщо вона веде в кінцевий пункт, а місто C вже проходили - виклик процедури виведення результату і останов.
Якщо поточна дуга веде в місто С, встановлюємо відповідну позначку (LabC = 1).
Позначаємо дугу як оброблену, заносимо її в масив SLED, який містить поточний оброблюваний шлях, KS - кількість елементів у ньому.
Викликаємо процедуру DFS від вершини (G [v, j]), в яку ведуть поточна дуга.
Перед виходом із процедури DFS відновлюємо стан, "початкове" перед її викликом: знімаємо позначку обробки з дуги (color [v, j]: = FREE), видаляємо її з масиву SLED (dec (ks), оператор sled [ks]: = 0; робити необов'язково, але він надає зручності налагодження - що б у масcіве SLED ненульовими були тільки реально входять в дорогу елементи).
І, нарешті, процедура виведення результату:
procedure OutRes;
begin
writeln (ks +2);
writeln (A); for i: = 1 to ks do writeln (sled [i]); writeln (B);
end;
Оскільки за алгоритмом побудови початковий (A) і кінцевий (B) міста не заносилися в масив SLED, то вони виводяться окремо, а кількість міст в дорозі (KS) перед виведенням збільшується на 2.
Повний текст програми наводиться нижче.
program by97d2s3;
const
FREE = 1;
DONE = 2;
var
G, color: array [1 .. 50,1 .. 100] of byte;
D: array [1 .. 50] of longint;
kG, sled: array [1 .. 50] of byte;
path: array [1 .. 100] of byte;
MinD, is: longint;
i, j, x, y, A, B, C, N, M, ks, LabC: byte;
Yes: boolean;
procedure OutRes;
begin
writeln (ks +2);
writeln (A); for i: = 1 to ks do writeln (sled [i]); writeln (B);
end;
procedure DFS (v: byte);
var j: byte;
begin
for j: = 1 to kG [v] do
if (color [v, j] = FREE)
then
begin
if (G [v, j] = B) and (LabC = 1)
then begin OutRes; halt; end;
if G [v, j] = C then LabC: = 1;
color [v, j]: = DONE; inc (ks); sled [ks]: = G [v, j];
DFS (G [v, j]);
color [v, j]: = FREE; sled [ks]: = 0; dec (ks);
if G [v, j] = C then LabC: = 0;
end;
end;
begin
assign (input, 'path.in'); reset (input);
assign (output, 'path.out'); rewrite (output);
read (N, M);
for i: = 1 to M do
begin
read (x, y); inc (kG [x]); G [x, kG [x]]: = y; color [x, kG [x]]: = FREE;
end;
read (A, B, C);
LabC: = 0;
DFS (A);
writeln (-1);
close (input); close (output);
nd.
3.2 Завдання "Перехрестя" (Автор - Котов В.М.)
Республіканська олімпіада з інформатики 1998
Дано декартові координати N перехресть міста, які пронумеровані від 1 до N. На кожному перехресті є світлофор. Деякі з перехресть з'єднані дорогами з двостороннім (правостороннім) рухом, які перетинаються тільки на перехрестях. Для кожної дороги відомо час, що потрібно для проїзду по ній від одного перехрестя до іншого.
Необхідно проїхати від перехрестя з номером А до перехрестя з номером В за мінімальний час.
Час проїзду залежить від набору проїжджаємо доріг і від часу очікування на перехрестях. Так, якщо ви під'їхали від перехрестя X до перехрестя C по дорозі Х-> C і хочете їхати далі по дорозі C-> У, то час очікування на перехресті C еавісіт від того, повертаєте ви наліво чи ні. Якщо ви повертаєте наліво, то час очікування одно D * К, де D дорівнює кількості доріг, що перетинаються на перехресті C, а К - деяка константа. Якщо ви не повертаєте наліво, то час очікування дорівнює нулю.
Написати програму, яка визначає найшвидший маршрут.
Специфікація вхідних даних.
Вхідні дані знаходяться у текстовому файлі з ім'ям PER.IN і мають наступну структуру:
- У першому рядку знаходиться число N (натуральне, <= 1000);
- У другому рядку - кількість доріг M (натуральне, <= 1000);
- В третьому рядку - константа K (натуральне число, <= 1000);
- В кожній з N наступних стpок знаходиться паpа чисел х і у, розділених пробілом, де x, y - кооpдінати перехрестя (цілі числа, не перевищувати за модулем 1000);
- В кожній з M наступних рядків знаходиться 3 числа p, r, t, розділені пробілом, де p і r - номери перехресть, які з'єднує дорога, а t (натуральне, <= 1000) - час проїзду по ній;
- В останньому рядку знаходяться номери початкового А і кінцевого У перехресть.
Специфікація вихідних даних.
Вихідні дані повинні бути записані в текстовий файл з ім'ям PER.OUT і мати наступний формат:
- У першому рядку знаходиться натуральне число T - час проїзду по найшвидшому маршруту;
- В кожній з наступних рядків знаходиться одне число - номер чергового перехрестя в маршруті (починаючи з перехрестя з номером А і закінчуючи В).
Коротко ідея рішення може бути викладена наступним чином.
Алгоритм Дейкстри безпосередньо не застосуємо, оскільки:
а) оптимальне рішення в наступної вершини не є сумою оптимального рішення для попередньої вершини і ваги поточного ребра
б) вагу поточного ребра - непостійна величина, що залежить від того, яким поворотом ми вийшли з вершини.
Тому пропонується рішення пошуком в глибину. При цьому дозволяється використовувати кожну вершину стільки разів, скільки у неї є ребер. Відсікаються рішення гірше поточного оптимального.
Можна ще прискорити рішення, якщо забезпечувати перебір, починаючи з правих поворотів (відсікання працювали б більш ефективно).
Зауваження:
У тексті задачі явно не вказано, але автори оригінальних тестів до завдання вважали, що поворот на 180 градусів - це лівий поворот (як в армії: поворот на 180 градусів - "через ліве плече").
Розглянемо рішення більш докладно:
Введення вихідних даних здійснюється наступним чином:
read (N, M, K);
for i: = 1 to N do readln (x [i], y [i]);
for i: = 1 to N do begin kG [i]: = 0; D [i]: = 0; end;
for i: = 1 to M do
begin
readln (p, r, t); inc (kG [p]); inc (kG [r]);
G [p, kG [p], 1]: = r; G [p, kG [p], 2]: = t; inc (D [p]);
G [r, kG [r], 1]: = p; G [r, kG [r], 2]: = t; Inc (D [r]);
end;
readln (vA, vB);
Тут
kG [i] - кількість дуг з вершини i
G [i, j, 1] - вершина, з якої сполучена вершина i дугою
з номером j у списку всіх дуг із вершини i.
$ G [i, j, 2] - час проїзду від вершини i до вершини, з якою вона з'єднана дугою j у списку всіх дуг із вершини i.
D [i] - кількість доріг, що перетинаються на перехресті i (тобто сумарна кількість дуг, що виходять з вершини i та входящік в неї)
vA і vB вказують, звідки і куди потрібно добиратися.
Оскільки за умовами задачі дороги двосторонні, то, для кожної введеної дороги, ми додаємо в граф дві дуги.
Основний алгоритм виглядає наступним чином:
for i: = 1 to N do
begin
for j: = 1 to kG [i] do Color [i, j]: = WHITE; {всі дуги вільні}
Time [i]: = maxlongint; {час маршруту до I - максимально}
end;
OptT: = Maxlongint; {Оптимальний час - максимально}
Sled [1]: = vA; {Заносимо в маршрут вершину vA}
kv: = 1; {Кількість вершин у маршруті = 1}
Time [vA]: = 0; {Оптимальний час до вершини vA = 0}
DFS (vA); {Пошук в глибину від вершини vA}
... висновок відповіді ...
Рекурсивна процедура DFS (i) забезпечує виконання Наступний роботи:
Procedure DFS (i)
...
begin
for j: = 1 to kG [i] do
if G [i, j, 1] <> vB
then
begin
if color [i, j] = FREE
then
begin
... продовження шляху з викликом DFS
end
end
else
begin
... порівняння шляху з поточним оптимальним
end;
end;
Якщо поточна вершина - кінцева (vB), то робимо "... порівняння шляху з оптимальним"
Інакше перевіряємо, якщо поточна дуга ще не використання (color [i, j] = FREE) щось робимо "... продовження шляху з викликом DFS".
При цьому, перед входом в DFS позначаємо дугу як використану (color [i, j]: = DONE), а після виходу - як знову вільну (color [i, j]: = FREE);
"... Вони йшли з викликом DFS" включає в себе наступні оператори:
inc (kv); sled [kv]: = G [i, j, 1]; {поміщаємо в дорогу нову вершину}
NewTime: = Time [i] + G [i, j, 2] + CountTime; {обчислюємо новий час}
if NewTime <OptT {якщо новий час менше оптимального}
then {то продовжуємо, інакше - відсікаємо}
begin
color [i, j]: = DONE; {Позначаємо - ребро використано}
RetTime: = Time [G [i, j, 1]]; {Зберігаємо старе час}
Time [G [i, j, 1]]: = NewTime; {Встановлюємо новий час}
DFS (G [i, j, 1]); {Викликаємо пошук від G [i, j, 1]}
Time [G [i, j, 1]]: = RetTime; {Відновлюємо старе час}
color [i, j]: = FREE; {Позначаємо - ребро вільно}
end;
Sled [kv]: = 0; dec (kv); {Видаляємо вершину з шляху}
Для обчислення нового часу тут використовується функція CounTime з параметром kv - номер останньої вершини в стеке.Ета функція робить наступне: Відновлює номери вершин, проходу через перехрестя "з i1 через i2 в i3":
if kv = 2 then begin CountTime: = 0; exit; end;
i1: = sled [kv-2];
i2: = sled [kv-1];
i3: = sled [kv];
if i3 = i1 then begin CountTime: = K * D [i2]; exit; end;
Попутно, з'ясовується
а) якщо вершин в дорозі всього 2, тобто не було ніякого повороту - це крок з початкової вершини і CountTime = 0.
б) якщо i1 = i3 то це поворот на 180 градусів і автори завдання вважають, що це теж лівий поворот, CountTime = K * D [i2]; де, K - коефіцієнт який вводиться, i2 - перехрестя, D [i2] - кількість доріг, що входять до цього перехрестя.
Потім з масивів координат перехресть вибираються координати поточних перехресть: (x1, y1) (звідки); (x2, y2) (через який); (x3, y3) (куди).
x1: = x [i1]; x2: = x [i2]; x3: = x [i3];
y1: = y [i1]; y2: = y [i2]; y3: = y [i3];
Обчислюється рівняння прямої по точках (x1, y1) та (x2, y2)
A: = y2-y1;
B: = x1-x2;
C: = y1 * (x2-x1)-x1 * (y2-y1);
Підстановкою координат (x3, y3) в рівняння прямої Ax + By + C = 0, побудованої за першими двох точках (x1, y1) та (x2, y2) і з урахуванням крайніх випадків, коли пряма паралельна осях координат, обчислюємо, чи був поворот лівим чи правим і відповідно встановлюємо значення функції CountTime.
Left: = (((x2> x1) and ((A * x3 + B * y3 + C) <0))) or
(((X2 <x1) and ((A * x3 + B * y3 + C) <0))) or
(((Y2 = y1) and (x1> x2) and (y3 <y1))) or
(((Y2 = y1) and (x1 <x2) and (y3> y1))) or
(((X2 = x1) and (y1> y2) and (x3> x1))) or
(((X2 = x1) and (y1 <y2) and (x3 <x1)));
if Left
then CountTime: = K * D [i2]
else CountTime: = 0;
"Порівняння шляху з оптимальним" виконується наступним чином:
inc (kv); sled [kv]: = G [i, j, 1];
T: = Time [i] + G [i, j, 2] + CountTime;
if T <OptT
then begin
OptT: = T;
OptKv: = kv; OptSled: = Sled;
end;
Sled [kv]: = 0; dec (kv);
Таким чином, оптимальний час зберігається у змінній OptT а оптимальний шлях зберігатися в масиві OptSled з кількістю елементів OptKv. І тому, виведення результатів виглядає так:
writeln (OptT);
for i: = 1 to OptKv do writeln (OptSled [i]);
Повний текст програми наводиться нижче

program by98d2s2;
Const
FREE = 1;
DONE = 2;
Type
int1000 = 0 .. 1000;
int3 = 1 .. 3;
var
G: array [1 .. 1000,1 .. 10,1 .. 2] of int1000;
Time, D: array [1 .. 1000] of longint;
x, y, kG, Sled, OptSled, divd: array [1 .. 1000] of int1000;
Color: array [1 .. 100,1 .. 10] of int3;
N, M, K, i, p, r, t, vA, vB, v, kv, OptKv, j: int1000;
OptT: longint;
function CountTime (i: int1000): longint;
var
Left: boolean;
i1, i2, i3: int1000;
x1, x2, x3, y1, y2, y3, A, B, C: longint;
begin
if kv = 2 then begin CountTime: = 0; exit; end;
i1: = sled [kv-2];
i2: = sled [kv-1];
i3: = sled [kv];
if i3 = i1 then begin CountTime: = K * D [i2]; exit; end;
x1: = x [i1]; x2: = x [i2]; x3: = x [i3];
y1: = y [i1]; y2: = y [i2]; y3: = y [i3];
A: = y2-y1;
B: = x1-x2;
C: = y1 * (x2-x1)-x1 * (y2-y1);
Left: = (((x2> x1) and ((A * x3 + B * y3 + C) <0))) or
(((X2 <x1) and ((A * x3 + B * y3 + C) <0))) or
(((Y2 = y1) and (x1> x2) and (y3 <y1))) or
(((Y2 = y1) and (x1 <x2) and (y3> y1))) or
(((X2 = x1) and (y1> y2) and (x3> x1))) or
(((X2 = x1) and (y1 <y2) and (x3 <x1)));
if Left
then CountTime: = K * D [i2]
else CountTime: = 0;
end;
Procedure DFS (i: int1000);
var
j: byte;
T: longint;
RetSled, RetPred, RetTime: longint;
begin
for j: = 1 to kG [i] do
if G [i, j, 1] <> vB
then
begin
if color [i, j] = FREE
then
begin
inc (kv); sled [kv]: = G [i, j, 1];
NewTime: = Time [i] + G [i, j, 2] + CountTime;
if NewTime <OptT
then
begin
color [i, j]: = DONE;
RetTime: = Time [G [i, j, 1]];
Time [G [i, j, 1]]: = NewTime;
DFS (G [i, j, 1]);
Time [G [i, j, 1]]: = RetTime;
color [i, j]: = FREE;
end;
Sled [kv]: = 0; dec (kv);
end
end
else
begin
inc (kv); sled [kv]: = G [i, j, 1];
T: = Time [i] + G [i, j, 2] + CountTime;
if T <OptT
then begin
OptT: = T;
OptKv: = kv; OptSled: = Sled;
end;
Sled [kv]: = 0; dec (kv);
end;
end;
begin
assign (input, 'PER.in'); reset (input);
assign (output, 'PER.out'); rewrite (output);
read (N, M, K);
for i: = 1 to N do readln (x [i], y [i]);
for i: = 1 to N do begin kG [i]: = 0; D [i]: = 0; end;
for i: = 1 to M do
begin
readln (p, r, t); inc (kG [p]); inc (kG [r]);
G [p, kG [p], 1]: = r; G [p, kG [p], 2]: = t; inc (D [p]);
G [r, kG [r], 1]: = p; G [r, kG [r], 2]: = t; Inc (D [r]);
end;
readln (vA, vB);
for i: = 1 to N do
begin
for j: = 1 to kG [i] do Color [i, j]: = FREE;
Time [i]: = maxlongint;
end;
Time [vA]: = 0; kv: = 1; Sled [1]: = vA; OptT: = Maxlongint;
DFS (vA);
writeln (OptT);
for i: = 1 to OptKv do writeln (OptSled [i]);
close (input); close (output);
end.

3.3 Завдання "Скрудж Мак-Дак" (Автор - Котов В.М.)

Республіканська олімпіада з інформатики 1995
Скрудж Мак-Дак вирішив зробити прилад для керування літаком. Як відомо, положення штурвала залежить від стану вхідних датчиків, але ця функція досить складна.
Його механік Гвинт Р. зробив пристрій, обчислює цю функцію в кілька етапів з використання проміжної пам'яті і допоміжних функцій. Для обчислення кожної з функцій потрібно, щоб у комірках пам'яті вже перебували обчислені параметри (які є значеннями обчислених функцій), необхідні для її обчислення. Обчислення функції без параметрів може проводиться в будь-який час.
Після обчислення функції осередки можуть бути використані повторно (хоча б для запису результату обчисленої функції). Структура виклику функцій така, що кожна функція обчислюється не більше одного разу і будь-який параметр використовується не більше одного разу. Будь-який параметр є ім'я функції. Так як Скрудж не хоче витрачати зайвих грошей на мікросхеми, він поставив завдання мінімізувати пам'ять приладу. За заданою структурі викликів функцій необхідно визначити мінімальний можливий розмір пам'яті приладу і вказати послідовність обчислення функцій.
Вхідний файл INPUT.TXT
Формат вводу:
1 рядок> <загальна кількість функцій N>
2 рядок> <ім'я функції, яку небхідно обчислити>
3 рядок> <ім'я функції>; <к-ть параметрів> [<список імен параметрів>]
...
N +2 рядок> <ім'я функції> <к-ть параметрів> [<список імен параметрів>]
Вихідний файл OUTPUT.TXT
Формат виводу:
Розмір пам'яті (в комірках)
ім'я 1-й обчислюється функції
ім'я 2-й обчислюється функції
....
ім'я функції, яку необхідно обчислити
Примітка: ім'я функції є натуральне число від 1 до N.
Приклад.
Введення Висновок
5 Розмір пам'яті: 2 осередки
1 Порядок:
1 2 2 3 Функція 4
2 0 Функція 5
3 2 4 5 Функція 3
4 0 Функція 2
5 0 Функція 1
У короткому викладі в даній задачі потрібно зробити наступне:
- Знайти S - максимальний ступінь серед тих вершин, що підлягають обходу (пошук в глибину DFS1)
- Необхідна пам'ять обчислюється за формулою
MaxS = S + k -1
де S - знайдено на попередньому кроці (DFS1),
а k - максимальна кількість вершин одного рівня вкладеності з такою ж S.
Для обчислення k відбувається обхід повторним пошуком в глибину DFS2
- Третім пошуком в глибину DFS3 знаходимо і поміщаємо в чергу V всі вершини, ступінь яких дорівнює S.
- Останнім, четвертим пошуком в глибину обходимо дане дерево в порядку вершин у черзі V. Тобто, в першу чергу обчислюємо ті функції, для яких потрібна максимальна пам'ять.
Розглянемо реалізацію алгоритму більш детально.
Введення вихідних даних здійснюється наступним чином:
Read (N, FC);
for i: = 1 to N do
begin
read (f, kG [f]);
for j: = 1 to kG [f] do read (G [f, j]);
end;
Тут
N - вихідне кількість вершин,
FC - номер функції, яку потрібно обчислити
kG [i] - кількість параметрів для обчислення функції i
G [i, j] - j-тий параметр, необхідний для обчислення функції i
Тіло головної програми виглядає так:
for i: = 1 to N do color [i]: = WHITE; {позначити вільними всі вершини}
DFS1 (FC); {знаходимо S - максимальний ступінь вершин використовуваних для обчислення функції FC}
MaxS: = S;
DFS2 (FC); {Обчислюємо
K - кількість вершин зі ступенем S в поточному шарі і
MaxS - максимальна із значень по шарах величина S + K-1}
kv: = 0;
DFS3 (FC); {Помістити в масив V всі вершини, ступінь яких дорівнює S, кількість таких вершин - у змінній kv}
kr: = 0;
for i: = 1 to kv do DFS4 (v [i]); {Обхід графа пошуком в глибину починаючи з вершин з максимальним ступенем з масиву V.
Знайдені вершини заносити в масив r}
writeln (MaxS); {виведення кількості осередків пам'яті}
for i: = 1 to kr do writeln (r [i]); {висновок порядку обчислення функцій}
Повне рішення завдання наводиться нижче
program by95d2t1;
const
WHITE = 1;
GRAY = 2;
BLACK = 3;
var
G: array [1 .. 100,1 .. 100] of longint;
kG, v, color, r: array [1 .. 100] of longint;
N, FC, i, j, s, f, kv, MaxS, kr: longint;
procedure DFS1 (u: longint);
var
i, k: longint;
begin
if s <kG [u] then s: = kG [u];
for i: = 1 to kG [u] do DFS1 (G [u, i]);
end;
procedure DFS2 (u: longint);
var
i, k: longint;
begin
k: = 0;
for i: = 1 to kG [u] do
if kG [G [i, j]] = s then inc (k);
if MaxS <s + k-1 then MaxS: = s + k-1;
for i: = 1 to kG [u] do DFS2 (G [u, i]);
end;
procedure DFS3 (u: longint);
var
i, k: longint;
begin
if kG [u] = s
then
begin
for i: = 1 to kG [u] do DFS3 (G [u, i]);
nc (kv); v [kv]: = u;
end;
end;
procedure DFS4 (u: longint);
var
i: longint;
begin
color [u]: = GRAY;
if kG [u] <> 0
then
for i: = 1 to kG [u] do
if color [G [u, i]] <> GRAY
then DFS4 (G [u, i]);
inc (kr); r [kr]: = u;
end;
begin
assign (input, 'input.txt'); reset (input);
assign (output, 'output.txt'); rewrite (output);
read (N, FC);
for i: = 1 to N do
begin
read (f, kG [f]);
for j: = 1 to kG [f] do read (G [f, j]);
end;
for i: = 1 to N do color [i]: = WHITE;
DFS1 (FC);
MaxS: = s; DFS2 (FC);
kv: = 0; DFS3 (FC);
kr: = 0; for i: = 1 to kv do DFS4 (v [i]);
writeln (MaxS);
for i: = 1 to kr do writeln (r [i]);
close (input); close (output);
end.

4. Сільносвязние компоненти й домінантні безлічі

Підграф графа називається його сільносвязной компонентою, якщо кожна з вершин подграфа досяжна від будь-якої з верші підграфа.
Домінантне безліч - це мінімальна безліч вершин графа, з якого досяжні всі вершини графа.
Алгоритми побудови сільносвязних компонент і домінантних множин графа розглянемо на прикладах.

4.1 Завдання "Картки"

Республіканська олімпіада з інформатики 1994
1. Є N карток. На кожній з них чорним чорнилом написаний її унікальний номер - число від 1 до N. Також на кожній картці червоними чорнилом написано ще одне ціле число, що лежить в проміжку від 1 до N (деякими однаковими "червоними" числами можуть позначатися декілька карток).
Наприклад, N = 5, 5 карток позначені наступним чином:
---------------
"Чорне" число | 1 | 2 | 3 | 4 | 5 |
---------------
"Червоне" число | 3 | 3 | 2 | 4 | 2 |
---------------
Необхідно вибрати з даних N карток максимальне число карток таким чином, щоб безлічі "червоних" і "чорних" чисел на них збігалися.
Для прикладу вище це будуть картки з "чорними" номерами 2, 3, 4 (безліч червоних номерів, як і потрібно у задачі, то ж - {2,3,4}).

ENTER: <N=> N, N <= 50
<"Чорний" номер 1, "червоний" -> "червоне" _чісло_1
......
<"Чорний" номер N, "червоний" -> "червоне" _чісло_N
ВИСНОВОК:
<В обраному безлічі елементів кількість елементів> S
<"Чорні" номери обраних карток> a1, ..., aS
Приклад введення Приклад виводу
6 червня
1 2 1
2 3 2
3 4 3
4 5 4
5 1 5
6 6 6
Фактично в задачі потрібно знайти всі сильно зв'язкові компоненти акредитуючої картками орієнтованого графа і додати до них картки, на яких і червоним і чорним кольором написані одні й ті самі числа.
Опишемо рішення завдання більш детально, спираючись на вихідний текст відповідної програми.
Тіло головній частині програми буде виглядати так:
readln (N); {Читаємо кількість карток}
M: = 0; {Кількість карток в результаті}
for i: = 1 to N do {Для всіх карток}
begin
readln (u, v); {Читаємо картка}
inc (kG [u]); G [u, kG [u]]: = v; {Поповнюємо по ній граф}
if u = v {Якщо червоне число дорівнює чорному}
then
begin inc (M); T [M]: = u; end; {заносимо картку в результат}
end;
BuildDominators; {Будуємо безліч домінаторів}
Reverse; {Звертаємо граф}
BuildDominators2; {Будуємо безліч домінаторів зверненого графа, попутно отримуючи все сільносвязние компоненти графа}
Розглянемо процедуру BuildDominators:
Procedure BuildDominators;
begin
Time: = 0; {Час обробки вершин = 0}
for i: = 1 to N do {Для кожної вершини i позначаємо}
begin
Color [i]: = WHITE; {Вершина вільна}
Dom [i]: = WHITE; {домінаторів немає}
CurC [i]: = WHITE; {Вільна на поточному кроці}
F [i]: = 0; {Час завершення обробки - 0}
end;
for v: = 1 to N do {Для всіх вершин}
if color [v] = {WHITE Якщо вершина v вільна}
then
begin
DFS (v); {Пошук в глибину від вершини v}
AddDominator (v); {Додати знайденого домінатора}
end;
end;
Таким чином в ході обробки вершин графа на цьому етапі у нас є 3 безлічі

Dom - знайдені на поточний момент Домінатори графа
Color - всі оброблені вершини
CurC - вершини, оброблені під час пошуку в глибину від поточної базової вершини V
Рекурсивна процедура пошуку в глибину виглядає для даної задачі таким чином:
Procedure DFS (i: byte);
var
j: byte;
begin
color [i]: = GRAY; {Вершина i оброблена взагалі}
curC [i]: = GRAY; {Вершина i оброблена на поточному кроці}
inc (Time); {Збільшити час обробки вершин}
for j: = 1 to kG [i] do {Поки у вершини є спадкоємці}
if curC [G [i, j]] = WHITE {Якщо спадкоємець не оброблявся на поточному кроці}
then DFS (G [i, j]); {Запустити пошук в глибину від спадкоємця}
inc (Time); {Збільшити час обробки вершин}
if F [i] = 0 {Якщо вершині i не встановлювалося час}
then F [i]: = Time; {завершення обробки - то встановити}
end;
Процедура AddDominator (v), також викликається з процедури BuildDominators, додає до списку домінаторів вершину v наступним чином: всі вершини, досягнуті з поточної (Curc [i] <> WHITE) викреслюємо з домінаторів; поточну додаємо до списку домінаторів (dom [Domi] : = GRAY;).

Procedure AddDominator (Domi: longint);
begin
for i: = 1 to N do {Для всіх вершин}
if (CurC [i] <> WHITE) {Якщо вона досягнута від поточної}
then dom [i]: = WHITE; {викреслюємо її з домінаторів}
dom [Domi]: = GRAY; {Вносимо поточну до списку домінаторів}
for i: = 1 to N do {Для всіх вершин встановлюючи позначку}
curC [i]: = WHITE; {недостигнуто від поточної}
end;
Наступний крок за побудовою домінаторів вихідного графа - транспонування графа (іншими словами зміна стрілок на дугах на протилежні). Це робиться за допомогою процедури Reverse. Процедура Reverse по вихідного графу G [1 .. N, 1 .. M], kg [1 .. N] будує граф B [1 .. N, 1 .. M], kB [1 .. N]:
Procedure Reverse;
begin
for i: = 1 to N do KB [i]: = 0; {Обнуляємо кількості дуг з вершини i}
for i: = 1 to N do
for j: = 1 to kG [i] do
begin
inc (kB [G [i, j]]); {інкрементіруем кількість дуг з вершини i}
b [G [i, j], kB [G [i, j ]]]:= i; {Додаємо в граф зворотний дугу}
end;
end;
Процедура BuildDominator2 будує безліч домінаторів на транспонованої графі, паралельно формуючи сильно зв'язкові компоненти (ССК) вихідного графа:

Procedure BuildDominators2;
begin
Time: = 0;
for i: = 1 to N do {Для всіх вершин}
begin
Color [i]: = WHITE; {Вільна взагалі}
CurC [i]: = WHITE; {Вільна в поточному циклі}
Kart [i]: = WHITE; {Не входить до ССК}
end;
SortF; {Сортуємо в порядку убування часи завершення обробки вершин при побудові множини домінаторів вихідного графа - результат Q [1 .. N]}
for v: = 1 to N do {У порядку убування часів обробки}
if color [Q [v]] = WHITE {Якщо вершина вільна}
then
begin
DFS2 (Q [v]); {Побудувати домінатора}
AddDominator2 (Q [v]); {Додати домінатора}
end;
for i: = 1 to N do {Для всіх вершин}
if (Kart [i] <> WHITE) {Якщо входить в СЗК}
then begin
inc (M); {вносимо в результат}
T [M]: = i;
end;
SortT; {Сортуємо результуюче безліч}
writeln (M); for i: = 1 to M do writeln (T [i]); {виводимо його}
end;

Процедура DFS2 пошуку глибину по транспонованої графу виглядає наступним чином:
Procedure DFS2 (i: byte);
Var j: byte;
begin
color [i]: = GRAY; curc [i]: = GRAY;
for j: = 1 to kB [i] do
if color [B [i, j]] = WHITE then DFS2 (B [i, j]);
end;
Процедура AddDominator2 забезпечує коректне побудова домінаторів транспонованої графа і сільносвязних компонент вихідного (і транспонованої) графів така:
Procedure AddDominator2 (Domi: longint);
var
MinT, MinK, KS: longint;
FlDom: boolean;
begin
KS: = 0; {Кількість вершин в поточній ССК}
for i: = 1 to N do {Для всіх вершин}
if Curc [i] <> WHITE {Якщо вершина досягнута}
then inc (KS); {інкрементіровать к-сть вершин в поточній ССК}
if ks> 1 {Якщо в поточній ССК вершин}
then {більше ніж одна}
for i: = 1 to N do {то внести їх усіх в KART}
if CurC [i] <> WHITE then Kart [i]: = GRAY;
for i: = 1 to N do curC [i]: = WHITE; {Зробити всі вершини вільними для нового кроку}
end;
Повний текст програми, вирішальної поставлене завдання наводиться нижче.
program by94d1t1;
Const
WHITE = 1;
GRAY = 2;
var
G, B: array [1 .. 100,1 .. 100] of byte;
Color, kG, kB, Dom, CurC, Kart: array [1 .. 100] of byte;
D, F, T, Q: array [1 .. 100] of longint;
N, i, j, Time, v, u, M, GRA_Y: longint;
Function max (a, b: longint): longint;
begin
if (a> b) then max: = a else max: = b
end;
Procedure Reverse;
begin
for i: = 1 to N do KB [i]: = 0;
for i: = 1 to N do
for j: = 1 to kG [i] do
begin
inc (kB [G [i, j]]);
b [G [i, j], kB [G [i, j ]]]:= i;
end;
end;
Procedure AddDominator (Domi: longint);
begin
for i: = 1 to N do
if CurC [i] <> WHITE then dom [i]: = WHITE;
dom [Domi]: = GRAY;
for i: = 1 to N do curC [i]: = WHITE;
end;
Procedure DFS (i: byte);
Var j: byte;
begin
color [i]: = GRAY; curC [i]: = GRAY; inc (Time);
for j: = 1 to kG [i] do
if curC [G [i, j]] = WHITE then DFS (G [i, j]);
inc (Time);
if F [i] = 0 then F [i]: = Time;
end;
Procedure BuildDominators;
begin
Time: = 0;
for i: = 1 to N do
begin
Color [i]: = WHITE; Dom [i]: = WHITE;
CurC [i]: = WHITE; F [i]: = 0;
end;
for v: = 1 to N do
if color [v] = WHITE
then begin DFS (v); AddDominator (v); end;
end;
Procedure SortF;
var
i, j, MaxD, k, Temp: longint;
begin
for i: = 1 to N do Q [i]: = i;
for i: = 1 to N-1 do
begin
MaxD: = F [i]; K: = i;
for j: = i +1 to N do
if F [j]> MaxD
then begin MaxD: = F [j]; k: = j; end;
F [k]: = F [i]; F [i]: = MaxD; Temp: = Q [k];
Q [k]: = Q [i]; Q [i]: = Temp;
end
end;
Procedure SortT;
var
i, j, MaxD, k, Temp: longint;
begin
for i: = 1 to M-1 do
begin
MaxD: = T [i]; K: = i;
for j: = i +1 to M do
if T [j] <MaxD
then begin MaxD: = T [j]; k: = j; end;
T [k]: = T [i]; T [i]: = MaxD;
end
end;
Procedure DFS2 (i: byte);
var
j: byte;
begin
color [i]: = GRAY; curc [i]: = GRAY;
for j: = 1 to kB [i] do
if color [B [i, j]] = WHITE then DFS2 (B [i, j]);
end;
Procedure AddDominator2 (Domi: longint);
var
MinT, MinK, KS: longint;
FlDom: boolean;
begin
KS: = 0;
for i: = 1 to N do
if Curc [i] <> WHITE then inc (KS);
if ks> 1
then
for i: = 1 to N do
if CurC [i] <> WHITE then Kart [i]: = GRAY;
for i: = 1 to N do curC [i]: = WHITE;
end;
Procedure BuildDominators2;
begin
Time: = 0;
for i: = 1 to N do
begin
Color [i]: = WHITE;
CurC [i]: = WHITE; Kart [i]: = WHITE;
end;
SortF;
for v: = 1 to N do
if color [Q [v]] = WHITE
then begin DFS2 (Q [v]); AddDominator2 (Q [v]); end;
for i: = 1 to N do
if (Kart [i] <> WHITE)
then begin inc (M); T [M]: = i; end;
SortT;
writeln (M);
for i: = 1 to M do writeln (T [i]);
end;
begin
assign (input, 'input.txt'); reset (input);
assign (output, 'output.txt'); rewrite (output);
readln (N);
M: = 0;
for i: = 1 to N do
begin
readln (u, v);
inc (kG [u]); G [u, kG [u]]: = v;
if u = v then begin inc (M); T [M]: = u; end;
end;
BuildDominators;
Reverse;
BuildDominators2;
close (input); close (output);
end.

4.2 Завдання "міжшкільна мережа"

IOI'96 - Міжнародна олімпіада з інформатики (Угорщина)
Деякі школи пов'язані комп'ютерною мережею. Між школами укладено угоди: кожна школа має список шкіл одержувачів, яким вона розсилає програмне забезпечення всякий раз, отримавши нове безкоштовне програмне забезпечення (ззовні мережі або з іншої школи). При цьому, якщо школа У є в списку одержувачів школи А, то школа А може і не бути в списку одержувачів школи В.
Потрібно написати програму, що визначає мінімальну кількість шкіл, яким треба передати по примірнику нового програмного забезпечення, що б поширити його по всіх школах мережі відповідно до угод (підзадача А).
Крім того, треба забезпечити можливість розсилки нового програмного забезпечення з будь-якої школи по всіх інших школах. Для цього розширити списки одержувачів деяких шкіл, додаючи в них нові школи. Потрібно знайти мінімальне сумарне кількість розширень списків, при яких програмне забезпечення з будь-якої школи досягло б решти шкіл (підзадача В). Одне розширення означає додавання однієї нової школи-одержувача в список одержувачів однієї зі шкіл.
Вхідні дані
Перший рядок файлу INPUT.TXT містить ціле число N - кількість шкіл в мережі (2 <= N <= 100). Школи нумеруються першими N позитивними цілими числами. Кожен з наступних N рядків визначає список одержувачів. Рядок i +1 містить номери одержувачів i-ї школи. Кожен такий список закінчується нулем. Порожній список містить тільки 0.
Вихідні дані
Ваша програма повинна записати 2 рядки в файл OUTPUT.TXT. Його перший рядок має містити одне позитивне ціле число - рішення підзадачі А. Другий рядок повинна містити вирішення підзадачі B.
Приклад введення і виведення
INPUT.TXT
5
2 4 3 0
4 5 0
0
0
1 0
OUTPUT.TXT
1
2
Подазадача A вимагає знайти домінантне безліч - тобто мінімальне безліч вершин вихідного графа, з якого є ланцюга до всіх інших вершин графа.
Обходом в глибину поки є вільні (неперекрашенние з білого в сірий колір) вершини знаходимо все, які досяжні з неї. І з безлічі домінант викреслюємо всі досяжні на поточному ходу і додаємо в безліч домінант вихідну вільну.
Підзадача B вимагає знайти кількість ребер, які потрібно додати, що б вихідний орієнтований граф став сильно зв'язковим. За вихідного графу будується транспонований (стрілки на дугах змінюються на протилежні). І для нового (транспонованої) графа знову будуються домінантне безліч.
Якщо і перше і друге домінантне безліч складаються з одного і того ж елемента - вершини 1, то граф був сільносвязний і додавати ребер не потрібно. Інакше треба додати максимум з елементів у побудованих домінантних множинах.
Оскільки практично майже таке ж завдання повністю розглянута в попередньому пункті, повний опис рішення тут не наводиться.
Інше рішення цієї ж завдання:
Методом Флойда будуємо матрицю досяжності.
У результаті всі вершини розбиваються на групи
- Витоки (у них немає входять дуг)
- Стоки (з них немає вихідних дуг)
- Проміжні (є й вхідні та вихідні)
Відповідь підзадачі A - кількість витоків
Для підзадачі B:
Якщо немає ні витоків, ні стоків, то граф - ССК (сільносвязная компоненту) та додавати ребер не потрібно. Інакше, вибираємо максимальне з кількостей витоків і стоків - це і є кількість ребер які потрібно додати.
Пояснення: Додаємо зв'язку між джерелами і стоками в результаті чого граф і стає сільносвязной компонентою

4.3 Завдання "Вінні-Пух і П'ятачок" (Автор - Котов В.М.)

Республіка, школярі, 1995р.
Вінні-Пух та П'ятачок найнялися захищати комп'ютерну мережу від хакерів, які викачували з комп'ютерів секретну інформацію. Комп'ютерна мережа Вінні-Пуха та П'ятачка складалася з пов'язаних між собою великих ЕОМ, до кожної з яких підключався декілька терміналів. Підключення до однієї з великих ЕОМ дозволяло отримати інформацію, що міститься в пам'яті цієї ЕОМ, а також всю інформацію, доступну для ЕОМ, до яких дана ЕОМ могла направляти запити. Хакерів і раніше нападали на подібні комп'ютерні мережі та їх тактика була відома. Тому Вінні-Пух та П'ятачок розробили спеціальну програму, яка допомогла вжити заходів проти готувався нападу.
Тактика хакерів.
При нападах хакерів завжди отримують доступ до інформації всіх ЕОМ мережі. Домагаються вони цього, захоплюючи деякі ЕОМ мережі, так щоб від них можна було запросити інформацію у решти ЕОМ. Варіантів захоплення існує безліч.
Наприклад, захопити всі ЕОМ. Але хакерів завжди вибирають такий варіант, при якому сумарна кількість терміналів у захоплених ЕОМ мінімально.
Примітка.
У мережі Вінні-Пуха та П'ятачка ні в яких 2-х ЕОМ кількість терміналів не збігається.
Технічне завдання.
Вам необхідно написати програму, вхідними даними якої було б опис мережі, а вихідними - список номерів ЕОМ, які можуть бути обрані хакерів для захоплення мережі згідно їхньої тактики. Введення здійснюється з файлу з ім'ям INPUT.TXT. Можливе введення з клавіатури.
Формат введення.
Кількість ЕОМ в мережі: N
ЕОМ # 1 має терміналів: T [1]
ЕОМ # 2 має терміналів: T [2]
...
ЕОМ # N має терміналів: T [N]
Права на запит:
A [1] B [1]
A [2] B [2]
...
A [K] B [K]
0 0
A [i] і В [i] - номери ЕОМ, останній рядок '0 0 'позначає кінець списку прав на запит, кожна пара A [i] B [i] позначає, що ЕОМ з номерів A [i] має право запитувати інформацію у ЕОМ з номером B [i] (A [i] не дорівнює B [i]).
При введенні числа N і T [i] - натуральні, T [i] <= 1000, N <= 50, K <= 2450.
Вхідні дані відповідають наведеним умовам.
Формат виводу.

Номери захоплюваних ЕОМ: С [1] C [2] ... З [M].
Кількість захоплюваних ЕОМ: <M>
Input.txt
5
100
2
1
3
10
3 січня
2 Березня
4 березня
4 Травня
4 Травня
0 0
Output.txt
1 квітня
2
Пошуком в глибину знаходимо безліч домінаторів мережі. (Витоки графа). Однак для оптимального вибору машин за кількістю терміналів необхідно вибрати кращу машину (з найменшою кількістю терміналів) в кожній сильно зв'язковий компоненті.
Для цього інвертуємо граф (міняємо напрямок всіх дуг графа на протилежне) і пошуком в глибину по інвертованому графу знаходимо всі його сильно зв'язкові компоненти (послідовні дерева, які виходять пошуком в глибину.). Нас цікавлять тільки сильно зв'язкові компоненти з кількістю вершин більше 1. У цьому випадку знаходимо з них вершину з мінімальною кількістю терміналів і замінюємо відповідний домінатор (якщо від цієї ССК раніше в Домінатори потрапила інша вершина).
Знову ж повне рішення не наводиться з причини його сильною близькості до повного вирішення завдань з двох попередніх пунктів.

5. Пошук у ширину

5.1 Завдання "Вулична гонка"
IOI'95 - Міжнародна олімпіада з інформатики (Голландія)
1 4 7
/ \ / \ / \
/ \ / \ / \
/ \ / \ / \
0 3 6 9
\ / \ / \ /
\ / \ / \ /
\ / \ / \ /
5 лютого <--- 8
Ви бачите точки, помічені числами від 0 до N (де N = 9), а також стрілки, що з'єднують їх. Точка 0 є стартовою; точка N - фінішної. Стрілками представлені вулиці з одностороннім рухом. Учасники гонки пересуваються від точки до точки по вулицях тільки в напрямку стрілок. У кожній точці учасник перегонів може вибрати будь-яку з вихідних стрілок.
Назвемо план вулиць хорошим, якщо він має такі властивості:
1. Кожна точка плану може бути досягнута зі старту.
2. Фініш може бути досягнутий з будь-якої точки плану.
3. У фінішу немає вихідних стрілок.
Для досягнення фінішу учасник не зобов'язаний пройти через всі точки. Однак деякі точки неможливо обійти. Назвемо їх неминучими. У прикладі такими точками є точки 0, 3, 6, 9. Для заданого хорошого плану Ваша програма повинна визначити безліч неминучих точок (за винятком старту і фінішу), які мають відвідати всі учасники (підзадача А).
Припустимо, що гонка повинна проводитися за два послідовних дня. Для цієї мети план повинен бути розбитий на два хороших плану, по одному на кожен день. У перший день стартом є точка 0, а фінішем служить якась "точка розбиття". У другий день старт знаходиться в цій точці розбиття, а фініш знаходиться в точці N. Для заданого хорошого плану Ваша програма повинна визначити безліч всіх можливих точок розбиття (підзадача В).
Точка S є точкою розбиття для гарного плану С, якщо S відрізняється від старту і фінішу С, і план розбивається нею на два хороших плану без загальних стрілок і з єдиною загальною точкою S. У прикладі точкою розбиття є точка 3.
ВХІДНІ ДАНІ
У файлі INPUT.TXT описаний хороший план, що містить не більше 50 пікселів і не більше 100 стрілок. У файлі є N +1 рядок. Перші N рядків містять кінцеві точки стрілок, що виходять відповідно з точок від 0 до N-1. Кожна з цих рядків закінчується числом -2. В останньому рядку міститься число -1.
ВИХІДНІ ДАНІ
Ваша програма повинна записати два рядки в файл OUTPUT.TXT. Перший рядок повинна містити кількість неминучих точок у заданому плані, після чого в тій же рядку повинні слідувати номери цих точок у будь-якому порядку (підзадача А). Другий рядок повинна містити кількість точок в заданому плані, за яким в тій же рядку повинні слідувати номери цих точок у будь-якому порядку (підзадача В).
ПРИКЛАД ВВЕДЕННЯ І ВИВЕДЕННЯ
INPUT.TXT OUTPUT.TXT
1 2 -2 2 3 6
3 -2 1 3
3 -2
5 4 -2
6 4 -2
6 -2
7 8 -2
9 -2
5 9 -2
-1
Для вирішення підзадачі A потрібно знайти всі неминучі точки. Це робиться пошуком в ширину - якщо після взяття вершини з черги чергу стає порожній - то ця вершина - неминуча.
Для вирішення підзадачі B потрібно перевірити кожну НЕМИНУЧЕ точку, чи є вона точкою розбиття. Для цього видаляємо цю точку з графа і пошуком в ширину будуємо 2 безлічі - досяжна точки від початку, і недосяжні точки від початку.
Якщо існує хоч одне ребро з недосяжною точки в досяжну - то поточна НЕМИНУЧЕ точка не є можливою точкою розбиття (за визначенням точки розбиття).
Розглянемо алгоритм вирішення задачі більш докладно, грунтуючись на вихідному тексті програми, вирішальної поставлене завдання:
Тіло головної програми виглядає наступним чином:
InputData; {Введення вихідних даних}
BFS; {Пошук у ширину - знаходимо "неминучі" точки}
OutSubA; {Висновок відповіді для підзадачі A}
for k: = 1 to SubA do {Для кожної неминучою точки}
begin
DelArcs (Duty [k]); {Видаляємо неминучу точку}
BFS; {Пошук у ширину -}
{Будуємо безлічі досяжних Color [i] = 1}
{І недосяжних Color [i] = 0 вершин}
RetArcs (Duty [k]); {Повертаємо неминучу крапку в граф}
for i: = 0 to N-1 do {Для всіх верішін}
begin
j: = 1; {j - номер дуги, що виходить з вершини}
while (a [i, j]> = 0) do {поки дуга існує}
begin
if (color [i] = WHITE) and {Якщо вихідна вершина досяжна}
(Color [a [i, j]] <> WHITE) {а "кінець дуги" - не досяжна}
then begin
Duty [k]: = 0; {Це - НЕ точка розбиття}
break {Перейти до перевірки наступного}
end; {неминучою точки}
inc (j); {Взяти наступну дугу}
end;
end;
end;
OutSubB; {Висновок відповіді для підзадачі B}
Процедура введення вихідних даних:
Procedure Inputdata;
begin
i: = 0; j: = 1; read (a [0,1]); {починаємо введення з вершини 0}
while (a [i, j] <> -1) do {Ще не закінчена обробка вершин}
begin
while (a [i, j] <> -2) do {Ще не закінчена обробка дуг}
begin
inc (j); {Збільшити номер дуги}
read (a [i, j]) {Читати дугу}
end;
if j> 2 then Sort; {Якщо дуг більше ніж одна, то}
{Відсортувати їх за зростанням}
{Номера кінцевої вершини}
readln; inc (i); {Перейти до зчитування наступного рядка}
j: = 1; read (a [i, j]); {Читати перший елемент рядка}
end; {Повернутися до початку циклу ПОКИ}
N: = i; {Занести в N номер останньої вершини}
end;
Пошук у ширину здійснюється за допомогою процедури BFS (Breadth-First Search).
procedure BFS;
var i, j: longint;
begin
for i: = 0 to N do color [i]: = WHITE; {Всі вершини вільні}
color [0]: = GRAY; {Початкова вершина - оброблена}
SubA: = 0; {Кількість неминучих вершин = 0}
BegQ: = 1; {Початок черги}
EndQ: = 0; {Черга порожня}
Put (0); {Помістити в чергу початкову вершину}
while (BegQ <= EndQ) do {Поки чергу не порожня}
begin
Get (i); {Взяти вершину i з черги}
if BegQ> EndQ {Якщо чергу залишилася порожньою}
then
begin
inc (SubA); {інкрементіровать кількість}
{Неминучих вершин}
Color [i]: = ColSubA {Позначити неминучу вершину}
end;
j: = 1; {номер дуги з вершини i}
while (a [i, j]> = 0) do {поки дуги не скінчилися}
begin
if color [a [i, j]] = WHITE {якщо вершина a [i, j] вільна}
then
begin
Put (a [i, j]); {поставити її в чергу}
color [a [i, j]]: = GRAY; {позначити вершину a [i, j]}
end; {як використану}
inc (j); {взяти наступну дугу}
end;
end;
end;
Висновок відповіді для підзадачі A і накопичення неминучих точок у масив Duty здійснюється так:
procedure OutSubA;
begin
SubA: = subA-2; {Початкова і кінцева точки не}
write (subA); {вважаються неминучими точками}
j: = 0; {J - кількість неминучих точок}
for i: = 1 to N-1 do {Для всіх вершин}
if (color [i] = ColSubA) {Якщо вона - неминуча}
then
begin
write ('', i); {Виводимо її}
inc (j); {інкрементіруем к-сть неминучих вершин}
Duty [j]: = i; {Запам'ятовуємо номер неминучою вершини}
end;
writeln;
end;
Далі наводиться повний текст рішення задачі
program io96d2t4;
Const
White = 1;
Gray = 2;
ColSubA = 4;
var
A: array [0 .. 101,0 .. 101] of integer;
Color, Q, Duty: array [0 .. 100] of integer;
N, i, j, subA, SubB, BegQ, EndQ, k: longint;
Procedure Put (x: longint);
Begin inc (EndQ); Q [EndQ]: = x; end;
Procedure Get (var x: longint);
Begin x: = Q [BegQ]; inc (BegQ); end;
Procedure Sort;
var
m, min, rmin, k: longint;
begin
for m: = 1 to j-2 do
begin
min: = a [i, m]; rmin: = m;
for k: = m +1 to j-1 do
if a [i, k] <min
then begin min: = a [i, k]; rmin: = k; end;
a [i, rmin]: = a [i, m]; a [i, m]: = min;
end;
end;
Procedure Inputdata;
begin
i: = 0; j: = 1; read (a [0,1]);
while (a [i, j] <> -1) do
begin
while (a [i, j] <> -2) do
begin inc (j); read (a [i, j]) end;
if j> 2 then Sort;
readln;
inc (i); j: = 1; read (a [i, j]);
end;
N: = i;
end;
procedure OutSubA;
begin
SubA: = subA-2; write (subA); j: = 0; Duty [0]: = 0;
for i: = 1 to N-1 do
if (color [i] = ColSubA)
then begin write ('', i); inc (j); Duty [j]: = i; end;
writeln;
end;
procedure BFS;
var i, j: longint;
begin
for i: = 0 to N do color [i]: = WHITE;
for i: = 0 to N do Q [i]: = 0;
color [0]: = GRAY; SubA: = 0; BegQ: = 1; EndQ: = 0;
Put (0);
while (BegQ <= EndQ) do
begin
Get (i);
if BegQ> EndQ
then begin inc (SubA); Color [i]: = ColSubA end;
j: = 1;
while (a [i, j]> = 0) do
begin
if color [a [i, j]] = WHITE
then begin
Put (a [i, j]);
color [a [i, j]]: = GRAY;
end;
inc (j);
end;
end;
end;
Procedure OutSubB;
begin
SubB: = 0;
for i: = 1 to SubA do
if Duty [i] <> 0 then inc (SubB);
write (subB);
for i: = 1 to SubA do
if Duty [i] <> 0 then write ('', Duty [i]);
writeln;
end;
Procedure DelArcs (k: longint);
begin
for j: = N Downto 1 do a [k, j +1]: = a [k, j];
a [k, 1]: =- 2;
for i: = 0 to N-1 do
begin
j: = 1;
while (a [i, j]> = 0) do
begin
if a [i, j] = k then a [i, j]: = maxint;
inc (j);
end;
end;
end;
Procedure RetArcs (k: longint);
begin
for i: = 0 to N-1 do
begin
j: = 1;
while (a [i, j]> = 0) do
begin
if a [i, j] = maxint then a [i, j]: = k;
inc (j);
end;
end;
for j: = 1 to N do a [k, j]: = a [k, j +1]
end;
begin
assign (input, 'input.txt'); reset (input);
assign (output, 'output.txt'); rewrite (output);
InputData;
BFS; {Пошук у ширину - знаходимо "неминучі" точки}
OutSubA;
for k: = 1 to SubA do
begin
DelArcs (Duty [k]); BFS; RetArcs (Duty [k]);
for i: = 0 to N-1 do
begin
j: = 1;
while (a [i, j]> = 0) do
begin
if (color [i] = WHITE) and (color [a [i, j]] <> WHITE)
then begin Duty [k]: = 0; break end;
inc (j);
end;
end;
end;
OutSubB;
close (input); close (output);
end.

6. Про розмірностях використаних в задачах масивів

Уважний читач звернув увагу, що в деяких випадках масиви оголошені меншої розмірності, ніж реально потрібно за умовами задачі. Це зроблено навмисно з таких міркувань.
A. На міжнародних олімпіадах з інформатики для школярів з липня 2001 року використовуються 32-бітові компілятори (Free Pascal та GNU C), для яких вирішуються розмірності декларуються масивів істотно більше ніж ті, що потрібні за умовами задачі.
B. Основна мета матеріалу - продемонструвати пропоновані базові алгоритми розв'язання задач на графах, і не хотілося ускладнювати матеріал, затемнюючи тим самим суть базових алгоритмів.
C. Можливо, методам ефективного використання оперативної пам'яті буде присвячена окрема стаття.

Висновок

Мета даного пункту коротко перерахувати наведені в даному матеріалі теоретичні відомості для вирішення задач на графах. Читач може використовувати цей перелік для самоконтролю успішності засвоєння даного матеріалу. Якщо Ви розумієте, що ховається під наведеною назвою і вмієте виконувати названу роботу - значить цілі, поставлені автором (і Вами) досягнуті.
Ось цей перелік питань для самоконтролю:
- Про зведення задач до графів
- Метод Флойда, застосовується для
- Пошуку найкоротших відстаней відстаней між усіма парами вершин графа (одночасно можна побудувати і самі найкоротші шляхи від кожної вершини до кожної)
- Побудови матриці досяжності графа
- Побудови безлічі витоків і безлічі стоків (витік - вершина, до якої немає входять дуг) (стік - вершина, з якої немає вихідних дуг)
- Метод Дейкстра, застосовується для
- Пошуку найкоротших відстаней від однієї вершини до всіх інших
- Побудови оптимальних маршрутів від однієї вершини до всіх інших
- Пошук у глибину, застосовується для
- Рішення довільних завдань на графах, що вимагають відповідного порядку обходу ("в глибину") графа;
- Побудови безлічі витоків і стоків (як витоків на транспонованої графі)
- Побудови сільносвязних компонент (ССК) графа ССК - це безліч вершин, кожна з яких досяжна зі всіх вершин ССК
- Пошук у ширину, застосовується для
- Рішення довільних завдань на графах, що вимагають відповідного порядку обходу ("в ширину") графа;

Література

1. М. Долинський "Пам'ятка учаснику олімпіади з інформатики серед школярів", "Радіоаматор. Ваш комп'ютер", Мінськ, No 1, 2000 с.20-21.
2. М. Долинський "Починаємо програмувати самостійно", "Радіоаматор. Ваш комп'ютер", Мінськ, No 1, 2000, с.23-24, No 2, 2000, с.22-23, No 3, 2000, с.23-25 , No 4, 2000, с.26-27, No 5, с.27-28, No 6, с.23-26.
3. М. Долинський "Рішення задач на тему" Геометрія на площині "," Радіоаматор. Ваш комп'ютер ", Мінськ, 2000, No 8, с.21-23, 2000, No 9, с.19-21.
4. М. Долинський "Розробка програм з використанням визначених програмістом типів даних", "Радіоаматор. Ваш комп'ютер", Мінськ, 2001, No 1, 2001, с.29-31, No 3, с. 26-28.
5. М. Долинський "Рішення задач за допомогою черги і стека", Мінськ, "Радіоаматор. Ваш комп'ютер", No 4, 2001, с.26-27, No 5, с.27-28, No 6, с.24-25 .
6. М. Долинський "Огляд можливостей мови програмування Паскаль", Москва, Радіомір. Ваш комп'ютер ", No 7, 2001, с.23-25.
7. М. Долинський "Алгоритми генерації комбінаторних об'єктів" "Радіоаматор. Ваш комп'ютер", (прийнята до публікації).
8. М. Долинський "Деякі факти з теорії чисел" "Радіоаматор. Ваш комп'ютер", (прийнята до публікації).
9. М. Долинський "Використання рекурсивних функцій і процедур" "Радіоаматор. Ваш комп'ютер", (прийнята до публікації).
10. М. Долинський "Рішення задач за допомогою рекурентних співвідношень" "Радіоаматор. Ваш комп'ютер", (прийнята до публікації).
11. Котов В.М., Волков І.А., Харитонович А.І. "Методи алгоритмізації" Навчальний посібник для 8-го класу загальноосвітньої школи з поглибленим вивченням інформатики, Мн., "Народна асвета", 1996, 127 с.
12. Котов В.М., Волков І.А., Лапо А.І. "Методи алгоритмізації" Навчальний посібник для 9-го класу загальноосвітньої школи з поглибленим вивченням інформатики, Мн., "Народна асвета", 1997, 160 с.
13. Котов В.М., Мельников О.І. "Методи алгоритмізації" Навчальний посібник для 10-11-го класів загальноосвітньої школи з поглибленим вивченням інформатики, Мн., "Народна асвета", 2000, 221 с.
Додати в блог або на сайт

Цей текст може містити помилки.

Програмування, комп'ютери, інформатика і кібернетика | Курсова
150.4кб. | скачати


Схожі роботи:
Алгоритми на графах Найкоротші відстані на графах
Рішення транспортних задач
Рішення економічних задач
Рішення задач за допомогою ЕОМ
Методи рішення логічних задач
Рішення задач по податковому забезпечення
Рішення задач способом пропорцій
Рішення задач симплекс методом
Рішення задач з курсу статистики
© Усі права захищені
написати до нас