Yalnız Ada#9044

Yalnız Ada (Editoryal)

Soruda belirtildiğine göre, bir adadan ayrıldığınızda ona bir daha asla dönemezsiniz. Dolayısıyla köşeleri adalar, kenarları köprüler olan graf, yönlü döngüsüz bir graf (DAG)'tır. Yalnızca giden köprüsü olmayan bir adasında, yani çıkış derecesi sıfır olan bir çıkmaz köşede (OutDegree[v] = 0) sıkışıp kalabilirsiniz.

prob[v], yürüyüş sırasında köşesine ulaşma olasılığını göstersin. Başlangıç köşesi için prob[r] = 1; diğer tüm köşeler için başlangıçta prob[v] = 0'dır. Graf döngü içermediğinden, her köşe en fazla bir kez ziyaret edilebilir.

köşesinden OutDegree[u] kadar kenar çıktığını varsayalım. 'da iken, bunlardan birini eşit olasılıkla rastgele seçeriz, yani u \to v kenarı boyunca prob[u] / OutDegree[u] olasılıkla ilerleriz. köşesine yalnızca doğrudan öncülerinden ulaşılabilir. Bu nedenle prob[v], 'ye giren tüm u \to v kenarlarının katkılarının toplamına eşittir:

prob[v] = \sum_{u \to v} prob[u] / OutDegree[u]

prob[v] değeri yalnızca tüm öncüllerinin olasılıkları kesinleştiğinde hesaplanabilir. Bu tam olarak topolojik sıralama'nın verdiği işleme sırasıdır: her köşe, kendisine kenar giden tüm köşelerden sonra gelir. Bu nedenle, topolojik sırayı Kahn algoritması kullanarak oluşturuyoruz ve aynı anda olasılıkları kenarlar boyunca "iletiyoruz".

Algoritma şu şekildedir:

Başlangıçta, kuyruk yalnızca başlangıç köşesini değil, giriş derecesi sıfır olan tüm köşeleri içerir. 'den ulaşılamayan köşeler de işlenir, ancak onların olasılığı sıfır kalır ve sonucu etkilemez.

Cevabı bulma. Çıkmaz köşeler arasında maksimum olasılık 'i bulun. Ardından, |prob[v] - mx| \le 10^{-9} koşulunu sağlayan tüm çıkmaz köşelerinin indekslerini artan sırada yazdırın. Olasılıklar kayan noktalı sayılarla yuvarlama hatalarıyla hesaplandığından, tolerans ile karşılaştırma gereklidir.

Karmaşıklık. Her köşe kuyruğa tam olarak bir kez eklenir ve çıkarılır, her kenar bir kez incelenir. Algoritmanın çalışma süresi O(n + m)'dir.

Örnek

Algoritmayı örnekteki grafik üzerinde simüle edelim. Her köşenin yanında, ona ulaşma olasılığı prob[v] gösterilmiştir. İşlenmiş köşeler gri ile gölgelendirilmiş, mevcut adımda olasılığın dağıtıldığı kenarlar mavi ile vurgulanmış, önceden incelenmiş kenarlar ise kesikli çizilmiştir.

Başlangıçta (sol şekil) prob[1] = 1'dir ve diğer tüm köşelerin olasılığı 'dır. Köşe , giriş derecesi sıfır olan tek köşedir, bu nedenle kuyruktan çıkarılan ilk köşe odur.

köşesinden dört kenar çıktığından, prob[1] = 1 olasılığı ve köşeleri arasında eşit olarak bölüştürülür: her biri 1 / 4 = 0.25 alır (sağ şekil). köşesinden çıkan kenarlar kaldırıldıktan sonra, ve köşelerinin giriş dereceleri sıfır olur ve kuyruğa eklenirler.

Sonra, köşesi işlenir. Ondan iki kenar çıktığından, prob[2] / 2 = 0.25 / 2 = 0.125 değeri ve 'e eklenir. prob[4] = prob[5] = 0.375 elde ederiz (sol alt şekil). Köşe 'in giriş derecesi sıfır olur ve kuyruğa eklenir.

Sırada köşe işlenir. Ondan tek bir kenar 3 \to 4 çıktığından, , prob[3] / 1 = 0.25 kadar artar ve olur (sağ alt şekil). Şimdi köşe de kuyruğa eklenir.

Köşe ve 'ün çıkan kenarı olmadığından, bunların işlenmesi herhangi bir olasılığı değiştirmez. Ortaya çıkan topolojik sıra 1, 2, 3, 5, 4'tür.

Çıkmaz köşeler ve 'tir, olasılıkları sırasıyla ve 'tir. Toplamları 'e eşittir: bir yerde sıkışıp kalacağımızdan eminiz. Maksimum olasılığa tek bir köşesinde ulaşılır, bu da cevaptır.

Algoritmanın uygulanması

sabitini tanımlayın — kayan noktalı sayıları karşılaştırmak için tolerans.

#define EPS 1e-9

Giriş grafiğini komşuluk listesi olarak saklayın. Köşelerin giriş ve çıkış derecelerini InDegree ve OutDegree dizilerinde saklayın. Kahn algoritmasında kuyruğu kullanılır. prob[v]'de köşesine ulaşma olasılığını hesaplıyoruz.

vector\<vector\<int\>\> g;
vector\<int\> InDegree, OutDegree;
deque\<int\> q;
vector\<double\> prob;

Ada sayısı 'i, köprü sayısı 'i ve başlangıç adasının indeksi 'yi okuyun. Komşuluk listesini ve derece dizilerini başlatın.

scanf("%d %d %d", &n, &m, &r);
g.assign(n + 1, vector\<int\>());
InDegree.assign(n + 1, 0);
OutDegree.assign(n + 1, 0);

Köprüleri okuyun ve grafiği oluşturun. Her a \to b kenarı için, köşesinin çıkış derecesini ve köşesinin giriş derecesini artırın.

for (i = 0; i < m; i++)
{
  scanf("%d %d", &a, &b);
  g[a].push_back(b);
  OutDegree[a]++;
  InDegree[b]++;
}

Giriş derecesi sıfır olan tüm köşeleri kuyruğa ekleyin — topolojik sıralama bunlarla başlar.

for (i = 1; i < InDegree.size(); i++)
  if (InDegree[i] == 0) q.push_back(i);

Başlangıç köşesi 'de olma olasılığı 'dir. Diğer tüm köşeler için başlangıç olasılığı 'dır: fonksiyonu dizisini sıfırlarla doldurur.

prob.resize(n + 1);
prob[r] = 1;

Kahn algoritmasını çalıştırın. Köşeler kuyruktan topolojik sırayla çıkarılır.

while (!q.empty())
{

Kuyruktan bir köşesi çıkarın. Bu ana kadar tüm öncülleri işlenmiş olduğundan, prob[v] değeri kesinleşmiştir. (v, to) kenarları üzerinde döngü yapın. köşesinden OutDegree[v] kadar kenar çıkar ve her biri eşit olasılıkla seçilir. Bu nedenle, v \to to kenarı boyunca prob[v] / OutDegree[v] olasılıkla ilerleriz. Bu değeri prob[to]'ya ekleyin.

  v = q.front(); q.pop_front();
  for (int to : g[v])
  {
    prob[to] += prob[v] / OutDegree[v];

(v, to) kenarını grafikten kaldırın — köşesinin giriş derecesini azaltın.

    InDegree[to]--;

köşesinin giriş derecesi sıfır olursa, tüm öncülleri işlenmiş demektir ve prob[to] değeri kesinleşir. 'yu kuyruğa ekleyin.

    if (InDegree[to] == 0) q.push_back(to);
  }
}

Çıkmaz köşeler — çıkan köprüsü olmayan adalar (OutDegree[i] = 0) — arasında maksimum olasılık 'i bulun.

mx = 0;
for (i = 1; i <= n; i++)
  if (OutDegree[i] == 0 && prob[i] > mx) mx = max(mx, prob[i]);

Olasılığı 'ten en fazla kadar farklı olan tüm çıkmaz köşelerin indekslerini artan sırada yazdırın. Böyle birden fazla köşe olabilir.

for (i = 1; i <= n; i++)
  if (OutDegree[i] == 0 && fabs(prob[i] - mx) <= EPS)
    printf("%d ", i);