Матч 313, Параллельное программирование (ParallelProgramming)

Дивизион 2, Уровень 3

 

Имеется сложная система, состоящая из нескольких работающих параллельно процессоров. Но некоторые процессоры могут начать работу только тогда, когда другие закончат свою работу. i - ый элемент массива time содержит время выполнения работы  i - ым процессором (в миллисекундах). Информация о зависимости выполнения работ содержится в массиве prec: prec[i][j]  содержит ‘Y’, если процессор j может начать работу только по завершению работы процессора i, и ‘N’, если время работы i - го и j - го процессоров не зависят друг от друга.

Необходимо найти наименьшее время (в миллисекундах), за которое все процессоры выполнят свои работы. Если работы выполнить невозможно, вернуть -1.

 

Класс: ParallelProgramming

Метод: int minTime(vector<int> time, vector<string> prec)

Ограничения: time и prec содержит одинаковое количество элементов (от 1 до 50), 1 £ time[i] £ 1000 строк, prec[i] содержит prec.size() символов ‘N’ или ‘Y’,prec[i][i] = ‘N’.

 

Вход. Массив time, содержащий время работы процессоров и массив prec, описывающий зависимость работы процессоров друг от друга.

 

Выход. Наименьшее время (в миллисекундах), необходимое для выполнения работ всеми процессорами. Если работы выполнить невозможно, вернуть -1.

 

Пример входа

time

prec

{150,200,250}

{"NNN",
"NNN",

"NNN"}

{150,200,250}

{"NNN",
"YNN",

"NNN"}

{150,200,250}

{"NYN",
"NNY",

"YNN"}

 

Пример выхода

250

350

-1

 

 

 

РЕШЕНИЕ

топологическая сортировка

 

Построим граф зависимостей работ процессоров и через поиск в глубину топологически отсортируем их в массиве top. Если граф имеет цикл, то топологическая сортировка невозможна, и метод minTime возвращает -1. Граф имеет цикл, если для некоторых i и j (i < j) существует ребро (top[i], top[j]). В массиве StartTime храним время начала работы процессоров, в EndTime – время окончания работы. Изначально положим StartTime[i] = 0,  EndTime[i] = time[i], 0 £  i < SIZE, где SIZE – число вершин в графе. В переменной p перебираем номера вершин в последовательности их  топологической сортировки, и для каждого ребра (p, j) устанавливаем StartTime[j] = max(StartTime[j], EndTime[p]). Присваивание говорит о том, что j - ая работа может начаться не раньше, чем закончится выполняться p - ая работа. Искомое время выполнения всех работ равно максимальному значению в массиве EndTime.

Другое решение задачи базируется на том факте, что если функция dfs(i) возвращает время окончания работы i - го процессора, то при наличии ребра (j, i) имеет место соотношение

EndTime[i] = max(EndTime[i], dfs(j) + time[i])

Оно означает, что если  i - ая работа зависит от j - ой, то время ее окончания не меньше времени окончания j - ой работы плюс время ее выполнения. Переменная Cycle устанавливается в 1, если в графе встречается цикл. Изначально used[i] = 0 (i - ая вершина белая). Равенство used[i] = 1 имеет место, если i - ая вершина встречалась в графе, но не была обработана до конца (является серой) и used[i] = 2, если i - ая вершина полностью обработана (черная). В ориентированном графе существует цикл, если существует ребро, ведущее в серую вершину.

 

ПРОГРАММА

 

#include <cstdio>

#include <vector>

#include <string>

#define MAX 51

using namespace std;

 

int m[MAX][MAX], used[MAX], top[MAX];

int SIZE, ptr;

 

void dfs(int i)

{

  used[i] = 1;

  for(int j = 0; j < SIZE; j++)

    if (m[i][j] && !used[j]) dfs(j);

  top[ptr++] = i;

}

 

class ParallelProgramming

{

public:

 

  int StartTime[MAX], EndTime[MAX];

 

  int minTime(vector<int> time, vector<string> prec)

  {

    int i, j, p;

    SIZE = prec.size();

    for(i = 0; i < SIZE; i++)

      for(j = 0; j < SIZE; j++)

        m[i][j] = (prec[i][j] == 'Y');

 

    ptr = 0;

    memset(used,0,sizeof(used));

    for(i = 0; i < SIZE; i++)

      if (!used[i]) dfs(i);

 

    for(i = 0; i < SIZE - 1; i++)

      for(j = i + 1; j < SIZE; j++)

        if (m[top[i]][top[j]]) return -1;

 

    for(i = 0; i < SIZE; i++) StartTime[i] = 0, EndTime[i] = time[i];

    for(i = SIZE - 1; i >= 0; i--)

    {

      p = top[i];

      EndTime[p] = StartTime[p] + time[p];

      for(j = 0; j < SIZE; j++)

        if (m[p][j]) StartTime[j] = max(StartTime[j],EndTime[p]);

    }

    for(p = i = 0; i < SIZE; i++)

      if (EndTime[i] > p) p = EndTime[i];

    return p;

  }

};

 

Реализация метода без топологической сортировки.

 

#include <cstdio>

#include <vector>

#include <string>

using namespace std;

#define MAX 51

 

int m[MAX][MAX],used[MAX];

int Cycle,res,SIZE;

vector<int> _time,EndTime;

 

int max(int i, int j)

{

  return (i > j) ? i : j;

}

 

int dfs(int i)

{

  if (used[i] == 2) return EndTime[i];

  if (used[i] == 1) {Cycle = 1; return 0;}

  used[i] = 1;

  for(int j=0;j<SIZE;j++)

    if (m[j][i]) EndTime[i] = max(EndTime[i],dfs(j)+_time[i]);

  used[i] = 2;

  return EndTime[i];

}

 

class ParallelProgramming{

public:

 

  int minTime(vector<int> time, vector<string> prec)

  {

    int i,j;

    SIZE = prec.size(); EndTime = _time = time;

    for(i=0;i<SIZE;i++)

      for(j=0;j<SIZE;j++)

        m[i][j] = (prec[i][j] == 'Y');

    Cycle = res = 0;

    memset(used,0,sizeof(used));

    for(i=0;i<SIZE;i++)

      if ((EndTime[i] = dfs(i)) > res) res = EndTime[i];

    return (Cycle) ? -1 : res;

  }

};