Graf#7618

Graf (Editoryal)

Sözlük sırasına göre en küçük topolojik sıralama, öncelik kuyruklu Kahn algoritması ile oluşturulur: her adımda tüm erişilebilir düğümler (henüz yazılmamış düğümlerden gelen hiçbir kenarı olmayan düğümler) arasından en küçük numaralı düğüm seçilir.

Eklenen bir u \to v kenarı, düğümü yazılana kadar düğümünü erişilemez yapar. Dolayısıyla kenar ekleyerek küçük düğümleri "gizleyebilir" ve böylece algoritmayı daha büyük numaralı düğümleri seçmeye zorlayabiliriz. Gizlenen her düğüm, bütçesinden bir kenara mal olur.

Açgözlü fikir. Cevabı pozisyon pozisyon oluştururuz. Sözlük sırasına göre karşılaştırma, ilk farklı pozisyon tarafından belirlenir; bu yüzden her pozisyona mümkün olan en büyük numaralı düğümü yerleştirmeliyiz — sonraki pozisyonlardaki hiçbir kazanç, mevcut pozisyondaki bir kaybı telafi edemez.

Mevcut adımda yazılabilecek düğümleri iki kümeye ayıralım:

kümesi, en küçük elemanı tepede olan bir öncelik kuyruğunda (min-heap), kümesi ise en büyük elemanı tepede olan bir öncelik kuyruğunda (max-heap) saklanır.

Bir düğümü ertelemek ne zaman kârlıdır. , 'daki en küçük düğüm olsun. Ona dokunmazsak, mevcut pozisyona yerleşir. 'i ertelemek (bir kenar harcamak) iki durumda anlamlıdır:

Diğer tüm durumlarda erteleme mevcut pozisyondaki sayıyı artırmaz ve kenar harcamaya gerek yoktur. Düğümleri, en küçüğünden başlayarak, kârlı olduğu sürece ve bütçesi tükenmediği sürece birer birer erteleriz.

Hangi düğüm yazılmalı. Ertelemeden sonra:

Ardından, Kahn algoritmasında her zamanki gibi, yazılan düğümün komşularının giriş derecelerini azaltırız; giriş derecesi sıfır olan düğümler 'ya eklenir.

Cevap neden doğrudur. Her ertelenmiş düğüm er ya da geç 'den yazılır ve o anda tam olarak bir kenar eklenir. Bu nedenle eklenen kenar sayısı erteleme sayısına eşittir ve 'yı aşmaz. Eklenen tüm kenarlar daha önce yazılmış bir düğümden daha sonra yazılmış bir düğüme gider; dolayısıyla bulunan sıra yeni grafın bir topolojik sıralamasıdır ve döngü oluşmaz.

Karmaşıklık. Her düğüm 'ya en fazla bir kez ve 'ye en fazla bir kez girer, grafın her kenarı bir kez incelenir. Toplam karmaşıklık O((n + m) \log n)'dir.

Problemin çözüm algoritması

Kahn algoritmasını ve iki öncelik kuyruğunu kullanırız:

Aşağıdaki dizileri tanımlayalım:

Adım 1. Başlatma

  1. Her düğümün giriş derecesini hesapla.

  2. Giriş derecesi sıfır olan tüm düğümleri 'ya ekle.

  3. 'yi ve listesini boş olarak başlat.

Adım 2. Cevabın oluşturulması

Tüm düğüm işlenmediği sürece:

1. Kârlıysa bir düğümü ertele.

Eklenen kenar sayısı 'dan küçük olduğu, yığını boş olmadığı ve aşağıdaki koşullardan en az biri sağlandığı sürece:

'dan en küçük düğümünü çıkar ve 'ye ekle. Bu düğüm daha sonra işlenecektir.

2. Sonraki düğümü seç.

'yi 'ın sonuna ekle.

3. Grafı güncelle.

Her çıkan v \to u kenarı için:

  1. indeg[u]'yu bir azalt.

  2. indeg[u] sıfır olursa, 'yu 'ya ekle.

Tüm düğümler 'a eklenene kadar bu adımları tekrarla.

Adım 3. Çıktı

  1. dizisini yazdır.

  2. Eklenen kenarların sayısını yazdır.

  3. Eklenen kenarların kendilerini yazdır.

Örnek

Problem metnindeki örneği ele alalım: n = 5, k = 2, kenarlar 1 \to 4, 4 \to 2, 1 \to 3. Başlangıç giriş dereceleri: ve düğümlerinin giriş derecesi , düğümlerinin giriş derecesi 'dir. Başlangıç kümeleri A = \{1, 5\}, B = \{\,\}'dir.

Adım

Ertelenen

Alınan

Kenar

Sıra

('da iki düğüm)

'dan

—

—

'den

5 \to 1

('da iki düğüm: ve )

'dan

—

— (k = 0)

'dan

—

5, 1, 4, 2

—

'den

2 \to 3

5, 1, 4, 2, 3

Adımları daha ayrıntılı inceleyelim.

5, 1, 4, 2, 3 sırasını ve eklenen 5 \to 1 ve 2 \to 3 kenarlarını elde ederiz. Orijinal graf (solda) ve eklenen kenarlarla graf (sağda, eklenen kenarlar kırmızı ile gösterilmiştir):

Kenar eklenmeden sözlük sırasına göre en küçük sıralama 1, 3, 4, 2, 5'tir: düğümü hiçbir şeye bağlı değildir, ancak büyük numarası nedeniyle en son seçilir. İki kenar eklendikten sonra düğümü ilk sıraya geçer ve 5, 1, 4, 2, 3 sırası sözlük sırasına göre daha büyüktür.

Bir örnek daha ele alalım; bu örnekte başlangıçta dört düğüm erişilebilirdir. n = 8, m = 6, k = 4 olsun ve grafın kenarları 6 \to 3, 4 \to 8, 3 \to 5, 1 \to 7, 2 \to 7, 5 \to 7 olsun.

1, 2, 4, 6 düğümlerine hiçbir kenar girmez, bu yüzden başlangıçta A = \{1, 2, 4, 6\}, B = \{\,\}'dir. Kenar eklenmeden sözlük sırasına göre en küçük sıralama 1, 2, 4, 6, 3, 5, 7, 8'dir.

Orijinal graf (solda) ve kenarlar eklendikten sonraki graf (sağda, eklenen kenarlar kırmızı ile gösterilmiştir)

Adım

önce

önce

Ertelenen

Alınan

Kenar

\{1, 2, 4, 6\}

'dan

—

\{4, 2, 1\}

3~(4 > 3)

'den

6 \to 4

\{3, 2, 1\}

—

'dan

—

\{3, 2, 1\}

—

'den

8 \to 3

—

'dan

—

—

'den

5 \to 2

—

'den

2 \to 1

—

'dan

—

Adımları daha ayrıntılı inceleyelim.

Cevap:

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

Bu örnek algoritmanın tüm durumlarını açıkça gösterir. . adımda tek seferde üç düğüm ertelenir — en az iki düğüm içerdiği sürece. . adımda ikinci koşul geçerli olur: yalnızca düğümünü içerir, ancak daha büyük olan düğümünü içerir, bu yüzden de ertelenir. Ertelenmiş düğümler 'den azalan sırada (4, 3, 2, 1) çıkar ve her biri kendisinden önce yazılan düğümden tam olarak bir kenar alır. Bu nedenle tam olarak erteleme sayısı kadar kenar eklenir — dört.

Algoritmanın gerçekleştirilmesi

Giriş verilerini okuyalım. Grafı oluşturalım. Komşuluk listeleri 'de, giriş dereceleri 'de saklanı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 öncelik kuyruğu tanımlayalım: — erişilebilir düğümler, en küçük eleman tepede (min-heap); — ertelenmiş düğümler, en büyük eleman tepede (max-heap).

priority_queue<int, vector<int>, greater<int>> A; // erisilebilir, min-heap
priority_queue<int> B;                            // ertelenmis, max-heap

Başlangıçta giriş derecesi sıfır olan tüm düğümler erişilebilirdir.

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

dizisi cevabı — aranan topolojik sıralamayı — biriktirir, dizisi ise eklenen kenarları saklar.

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

Döngünün her iterasyonu, tüm düğüm yazılana kadar cevaba bir düğüm ekler.

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

Düğümleri erteleriz. Bütçede kenar kaldığı ve kümesi boş olmadığı sürece, en küçük erişilebilir düğümünü ele alırız. en az bir düğüm daha içeriyorsa veya daha büyük numaralı bir ertelenmiş düğüm içeriyorsa onu ertelemek kârlıdır. Ertelenen düğüm 'dan 'ye taşınır ve bütçesi azalır. Aksi hâlde erteleme durur.

  // karli oldugu ve kenar kaldigi surece dugumleri erteliyoruz
  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;
  }

Mevcut pozisyona yazılacak düğümünü seçeriz. boş değilse, bu en küçük erişilebilir düğümdür. Aksi hâlde en büyük ertelenmiş düğümü alır ve son yazılan order.back() düğümünden 'ye bir kenar ekleriz.

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

'yi cevaba ekleriz ve Kahn algoritmasında olduğu gibi komşularının giriş derecelerini azaltırız. Giriş derecesi sıfır olan düğümler erişilebilir hâle gelir.

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

Bulunan topolojik sıralamayı, eklenen kenarların sayısını ve kenarların kendilerini yazdırırız.

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);