Qraf#7618

Qraf (Həll təhlili)

Leksikoqrafik ən kiçik topoloji sıralama prioritet növbəsi ilə Kan alqoritmi vasitəsilə qurulur: hər addımda bütün əlçatan təpələr (hələ yazılmamış təpələrdən daxil olan tili olmayan təpələr) arasından ən kiçik nömrəli təpə seçilir.

Əlavə edilmiş u \to v tili təpəsini təpəsi yazılana qədər əlçatmaz edir. Deməli, til əlavə etməklə kiçik təpələri "gizlədə" və bununla da alqoritmi daha böyük nömrəli təpələri seçməyə məcbur edə bilərik. Hər gizlədilmiş təpə ehtiyatından bir tilə başa gəlir.

Acgöz ideya. Cavabı mövqe-mövqe qururuq. Leksikoqrafik müqayisə ilk fərqlənən mövqe ilə müəyyən olunur, ona görə də hər mövqedə mümkün olan ən böyük nömrəli təpəni yerləşdirməliyik — sonrakı mövqelərdəki heç bir qazanc cari mövqedəki itkini kompensasiya edə bilməz.

Cari addımda yazıla bilən təpələri iki çoxluğa bölək:

çoxluğu ən kiçik elementi yuxarıda olan prioritet növbəsində (min-yığın), çoxluğu isə ən böyük elementi yuxarıda olan prioritet növbəsində (max-yığın) saxlanılır.

Təpəni nə vaxt təxirə salmaq sərfəlidir. Tutaq ki, — -dakı ən kiçik təpədir. Ona toxunmasaq, cari mövqeyə yerləşəcək. -i təxirə salmağın (bir til xərcləməyin) iki halda mənası var:

Bütün digər hallarda təxirə salmaq cari mövqedəki ədədi artırmır və til xərcləməyə ehtiyac yoxdur. Təpələri bir-bir, ən kiçikdən başlayaraq, sərfəli olduqca və ehtiyatı tükənməyincə təxirə salırıq.

Hansı təpəni yazmaq lazımdır. Təxirə salmadan sonra:

Sonra, Kan alqoritmində olduğu kimi, yazılmış təpənin qonşularının giriş dərəcələrini azaldırıq; giriş dərəcəsi sıfıra bərabər olan təpələr -ya əlavə olunur.

Cavab niyə düzgündür. Hər təxirə salınmış təpə gec-tez -dən yazılır və həmin anda düz bir til əlavə olunur. Buna görə əlavə edilmiş tillərin sayı təxirə salmaların sayına bərabərdir və -dan çox deyil. Bütün əlavə edilmiş tillər əvvəl yazılmış təpədən sonra yazılmış təpəyə gedir, ona görə də tapılmış sıra yeni qrafın topoloji sıralamasıdır və heç bir dövr yaranmır.

Mürəkkəblik. Hər təpə -ya ən çox bir dəfə və -yə ən çox bir dəfə daxil olur, qrafın hər tilinə isə bir dəfə baxılır. Ümumi mürəkkəblik O((n + m) \log n)-dir.

Məsələnin həlli alqoritmi

Kan alqoritmindən və iki prioritet növbəsindən istifadə edirik:

Aşağıdakı massivləri elan edək:

Addım 1. İnisializasiya

  1. Hər təpənin giriş dərəcəsini hesablamaq.

  2. Giriş dərəcəsi sıfır olan bütün təpələri -ya əlavə etmək.

  3. -ni və siyahısını boş olaraq inisializasiya etmək.

Addım 2. Cavabın qurulması

Bütün təpə emal olunmayana qədər:

1. Sərfəlidirsə, təpəni təxirə salmaq.

Əlavə edilmiş tillərin sayı -dan az olduqca, yığını boş olmadıqca və aşağıdakı şərtlərdən ən azı biri ödəndikcə:

-dan ən kiçik təpəsini çıxarıb -yə əlavə etmək. Bu təpə sonra emal olunacaq.

2. Növbəti təpəni seçmək.

-ni -ın sonuna əlavə etmək.

3. Qrafı yeniləmək.

Hər çıxan v \to u tili üçün:

  1. indeg[u]-nu bir vahid azaltmaq.

  2. Əgər indeg[u] sıfıra bərabər olarsa, -nu -ya əlavə etmək.

Bütün təpələr -a əlavə olunana qədər bu addımları təkrarlamaq.

Addım 3. Çıxış

  1. ardıcıllığını çap etmək.

  2. Əlavə edilmiş tillərin sayını çap etmək.

  3. Əlavə edilmiş tillərin özlərini çap etmək.

Nümunə

Şərtdəki nümunəyə baxaq: n = 5, k = 2, tillər 1 \to 4, 4 \to 2, 1 \to 3. İlkin giriş dərəcələri: və təpələrinin giriş dərəcəsi , təpələrinin giriş dərəcəsi -dir. İlkin çoxluqlar: A = \{1, 5\}, B = \{\,\}.

Addım

Təxirə salınan

Götürülür

Til

Sıra

(-da iki təpə)

, -dan

—

—

, -dən

5 \to 1

(-da iki təpə: və )

, -dan

—

— (k = 0)

, -dan

—

5, 1, 4, 2

—

, -dən

2 \to 3

5, 1, 4, 2, 3

Addımlara daha ətraflı baxaq.

5, 1, 4, 2, 3 sırasını və əlavə edilmiş 5 \to 1 və 2 \to 3 tillərini alırıq. İlkin qraf (solda) və əlavə edilmiş tillərlə qraf (sağda, əlavə edilmiş tillər qırmızı rənglə göstərilib):

Əlavə tillər olmadan leksikoqrafik ən kiçik sıralama 1, 3, 4, 2, 5-dir: təpəsi heç nə ilə əlaqəli deyil, lakin böyük nömrəsinə görə sonuncu seçilir. İki til əlavə edildikdən sonra təpəsi birinci olur və 5, 1, 4, 2, 3 sırası leksikoqrafik olaraq daha böyükdür.

Daha bir nümunəyə baxaq, burada əvvəlcə dörd təpə əlçatandır. Tutaq ki, n = 8, m = 6, k = 4, qrafın tilləri isə 6 \to 3, 4 \to 8, 3 \to 5, 1 \to 7, 2 \to 7, 5 \to 7-dir.

1, 2, 4, 6 təpələrinə heç bir til daxil olmur, ona görə də əvvəlcə A = \{1, 2, 4, 6\}, B = \{\,\}. Əlavə tillər olmadan leksikoqrafik ən kiçik sıralama 1, 2, 4, 6, 3, 5, 7, 8-dir.

İlkin qraf (solda) və tillər əlavə edildikdən sonrakı qraf (sağda, əlavə edilmiş tillər qırmızı rənglə göstərilib)

Addım

əvvəl

əvvəl

Təxirə salınan

Götürülür

Til

\{1, 2, 4, 6\}

, -dan

—

\{4, 2, 1\}

3~(4 > 3)

, -dən

6 \to 4

\{3, 2, 1\}

—

, -dan

—

\{3, 2, 1\}

—

, -dən

8 \to 3

—

, -dan

—

—

, -dən

5 \to 2

—

, -dən

2 \to 1

—

, -dan

—

Addımlara daha ətraflı baxaq.

Cavab:

6 4 8 3 5 2 1 7
4
6 4
8 3
5 2
2 1

Bu nümunə alqoritmin bütün hallarını aydın göstərir. -ci addımda birdən üç təpə təxirə salınır — -da ən azı iki təpə olduqca. -ci addımda ikinci şərt işləyir: -da yalnız təpəsi var, lakin -də daha böyük təpəsi var, ona görə də də təxirə salınır. Təxirə salınmış təpələr -dən azalan sırada (4, 3, 2, 1) çıxır və onların hər biri özündən əvvəl yazılmış təpədən düz bir til alır. Buna görə də düz təxirə salmaların sayı qədər til əlavə olunur — dörd.

Alqoritmin reallaşdırılması

Giriş verilənlərini oxuyuruq. Qrafı qururuq. Qonşuluq siyahıları -də, giriş dərəcələri -də saxlanılır.

scanf("%d %d %d", &n, &m, &k);
vector<vector<int>> g(n + 1);
vector<int> indeg(n + 1, 0);
for (int i = 0; i < m; i++)
{
  scanf("%d %d", &u, &v);
  g[u].push_back(v);
  indeg[v]++;
}

İki prioritet növbəsi elan edək: — əlçatan təpələr, ən kiçik element yuxarıda (min-yığın); — təxirə salınmış təpələr, ən böyük element yuxarıda (max-yığın).

priority_queue<int, vector<int>, greater<int>> A; // əlçatan, min-yığın
priority_queue<int> B;                            // təxirə salınmış, max-yığın

Əvvəlcə giriş dərəcəsi sıfır olan bütün təpələr əlçatandır.

for (int v = 1; v <= n; v++)
  if (indeg[v] == 0) A.push(v);

massivində cavab — axtarılan topoloji sıralama toplanır, massivində isə əlavə edilmiş tillər saxlanılır.

vector<int> order;
order.reserve(n);
vector<pair<int, int>> added;

Dövrün hər iterasiyası cavaba bir təpə əlavə edir, bütün təpə yazılana qədər.

while ((int)order.size() < n)
{

Təpələri təxirə salırıq. Ehtiyatda til qaldıqca və çoxluğu boş olmadıqca, ən kiçik əlçatan təpəsinə baxırıq. Əgər -da ən azı daha bir təpə varsa və ya -də daha böyük nömrəli təxirə salınmış təpə varsa, onu təxirə salmaq sərfəlidir. Təxirə salınmış təpə -dan -yə köçürülür, ehtiyatı isə azalır. Əks halda təxirə salma dayanır.

  // sərfəli olduqca və til ehtiyatı qaldıqca təpələri təxirə salırıq
  while (k > 0 && !A.empty())
  {
    int x = A.top();
    if (A.size() >= 2 || (!B.empty() && B.top() > x))
    {
      A.pop();
      B.push(x);
      k--;
    }
    else break;
  }

Cari mövqeyə yazılacaq təpəsini seçirik. Əgər boş deyilsə, bu, ən kiçik əlçatan təpədir. Əks halda ən böyük təxirə salınmış təpəni götürürük və sonuncu yazılmış order.back() təpəsindən -yə til əlavə edirik.

  int v;
  if (!A.empty())
  {
    v = A.top();
    A.pop();
  }
  else
  {
    v = B.top();
    B.pop();
    added.push_back(make_pair(order.back(), v));
  }

-ni cavaba əlavə edirik və Kan alqoritmində olduğu kimi onun qonşularının giriş dərəcələrini azaldırıq. Giriş dərəcəsi sıfıra bərabər olan təpələr əlçatan olur.

  order.push_back(v);
  for (int to : g[v])
    if (--indeg[to] == 0) A.push(to);
}

Tapılmış topoloji sıralamanı, əlavə edilmiş tillərin sayını və tillərin özlərini çap edirik.

for (int i = 0; i < n; i++)
  printf("%d ", order[i]);
printf("\n%d\n", added.size());
for (auto &e : added)
  printf("%d %d\n", e.first, e.second);