Güclü əlaqəlilik#2403

Güclü əlaqəlilik (Həll təhlili)

Məsələdə yönlü qrafın güclü bağlı komponentlərinin sayını tapmaq tələb olunur. Bunun üçün iki dərinliyinə axtarış yerinə yetirən Kosaraju alqoritmindən istifadə edirik:

Tərs qrafın ilkin qrafla eyni güclü bağlı komponentləri var: əgər -dan -yə və -dən -ya yollar varsa, onda kənarlar tərsinə çevrildikdən sonra bu yollar qorunur, yalnız onların istiqaməti dəyişir. İkinci gəzintidə təpələrin emal ardıcıllığı dərinliyinə axtarışın cari komponentdən kənara çıxmamasını təmin edir: onun çata biləcəyi bütün komponentlər bu anadək artıq ziyarət edilmişdir.

Cavab ikinci gəzintidə dərinliyinə axtarışın başlanğıc sayıdır.

Misal

Misallarda verilmiş qraflar aşağıda göstərilmişdir. Güclü bağlı komponentlər rənglə qeyd olunub.

Birinci qraf heç bir dövr saxlamır: hər hansı təpəyə qayıtmaq mümkün deyil. Buna görə də hər təpə ayrıca güclü bağlı komponent təşkil edir: , , .

İkinci qrafda , və təpələri 1 \to 2 \to 3 \to 1 dövrü üzərində yerləşir və bir komponent \{1, 2, 3\} təşkil edir. təpəsinə təpəsindən çatmaq mümkün olmadığından, və təpələri ayrı və komponentlərini təşkil edir. Beləliklə, hər bir qrafda güclü bağlı komponent var.

İkinci qraf üzərində alqoritmi simulyasiya edək. təpəsindən birinci dərinliyinə axtarış 1 \to 2 \to 3 yolunu, sonra isə təpəsindən 4 \to 5 yolunu izləyir. Təpələrin emalının başa çatma ardıcıllığı top = (3, 2, 1, 5, 4) olur. Tərsinə çevirdikdən sonra (4, 5, 1, 2, 3) alırıq.

Tərs qraf 2 \to 1, 3 \to 2, 1 \to 3 və 5 \to 4 kənarlarını ehtiva edir. İkinci gəzintini (4, 5, 1, 2, 3) ardıcıllığı ilə yerinə yetirək:

və təpələri artıq ziyarət edildiyindən, yeni başlanğıc yoxdur. Biz c = 3 komponent alırıq.

Qeyd edək ki, tərs qrafda başqa komponentə aid olan təpəsinə təpəsindən çatmaq mümkündür. Lakin təpəsi təpəsindən əvvəl emal edildiyindən, çağırıldığı anda o artıq ziyarət edilmiş olur və təpəsinin komponentinə düşmür.

Alqoritmin reallaşdırılması

Giriş qrafı qonşuluq siyahısı kimi, tərs qrafı isə qonşuluq siyahısı kimi saxlayırıq. massivi ziyarət edilmiş təpələri işarələyir. massivi birinci dərinliyinə axtarış zamanı təpələrin emalının başa çatma ardıcıllığını saxlayır.

vector<vector<int> > g, gr;
vector<int> used, top;

dfs1 funksiyası giriş qrafı üzərində dərinliyinə axtarış yerinə yetirir. təpəsi ondan əlçatan bütün təpələr emal edildikdən sonra, yəni ondan çıxan anda massivinin sonuna əlavə olunur.

void dfs1(int v)
{
  used[v] = 1;
  for (int to : g[v])
    if (!used[to]) dfs1(to);
  top.push_back(v);
}

dfs2 funksiyası tərs qraf üzərində dərinliyinə axtarış yerinə yetirir. Əsas proqramdan -nin bir çağırışı zamanı ziyarət edilən bütün təpələr bir güclü bağlı komponent təşkil edir.

void dfs2(int v)
{
  used[v] = 1;
  for (int to : gr[v])
    if (!used[to]) dfs2(to);
}

Proqramın əsas hissəsi. Giriş qrafını oxuyun. Hər a \to b kənarını qrafına, əks b \to a kənarını isə tərs qrafına əlavə edin.

scanf("%d %d", &n, &m);
g.resize(n + 1);
gr.resize(n + 1);
for(i = 1; i <= m; i++)
{
  scanf("%d %d", &a, &b);
  g[a].push_back(b);
  gr[b].push_back(a);
}

Giriş qrafı üzərində hələ ziyarət edilməmiş hər təpədən dərinliyinə axtarışa başlayın. Bundan sonra massivi qrafın bütün təpələrini emalının başa çatma ardıcıllığı ilə ehtiva edir.

used.resize(n+1);
for(i = 1; i <= n; i++)
  if (!used[i]) dfs1(i);

İkinci gəzintidən əvvəl massivini sıfırlayın. Tərs qrafın təpələri emalının başa çatma ardıcıllığının tərsinə baxılmalıdır, yəni massivi sondan əvvələ doğru taranmalıdır. Rahatlıq üçün massivini tərsinə çevirin.

used.assign(n + 1, 0);
reverse(top.begin(), top.end());

dəyişəni güclü bağlı komponentlərin sayını hesablayır.

c = 0;

massivi üzərində soldan sağa hərəkət edin. Hələ ziyarət edilməmiş hər təpəsindən tərs qraf üzərində dfs2 dərinliyinə axtarışına başlayın. O, dəqiq bir yeni güclü bağlı komponenti gəzir, buna görə -ni artırın.

for (int v : top)
  if (!used[v])
  {
    dfs2(v);
    c++;
  }

Qrafın güclü bağlı komponentlərinin sayını çap edin.

printf("%d\n",c);

Java reallaşdırılması

import java.util.*;

public class Main
{
  static ArrayList<Integer>[] g, gr;
  static boolean used[];
  static Vector<Integer> top = new Vector<Integer>();

  static void dfs1(int v)
  {
    used[v] = true;
    for(int i = 0; i < g[v].size(); i++)
    {
      int to = g[v].get(i);
      if (!used[to]) dfs1(to);
    }
    top.add(v);
  }

  static void dfs2(int v)
  {
    used[v] = true;
    for(int i = 0; i < gr[v].size(); i++)
    {
      int to = gr[v].get(i);
      if (!used[to]) dfs2(to);
    }
  }

  @SuppressWarnings("unchecked")
  public static void main(String[] args)
  {
    Scanner con = new Scanner(System.in);
    int n = con.nextInt();
    int m = con.nextInt();

    g = new ArrayList[n+1];
    for(int i = 0; i <= n; i++)
      g[i] = new ArrayList<Integer>();
    gr = new ArrayList[n+1];
    for(int i = 0; i <= n; i++)
      gr[i] = new ArrayList<Integer>();

    for (int i = 0; i < m; i++)
    {
      int a = con.nextInt();
      int b = con.nextInt();
      g[a].add(b);
      gr[b].add(a);
    }
    used = new boolean[n+1];
    for(int i = 1; i <= n; i++)
      if (!used[i]) dfs1(i);

    used = new boolean[n+1];
    int c = 0;
    for(int i = 1; i <= n; i++)
    {
      int v = top.get(n-i);
      if (!used[v])
      {
        dfs2(v);
        c++;
      }
    }

    System.out.println(c);
    con.close();
  }
}

Python reallaşdırılması

dfs1 funksiyası giriş qrafı üzərində dərinliyinə axtarış yerinə yetirir. təpəsi ondan əlçatan bütün təpələr emal edildikdən sonra, yəni ondan çıxan anda siyahısının sonuna əlavə olunur.

def dfs1(v):
  global g, used, top
  used[v] = True
  for to in g[v]:
    if not used[to]: dfs1(to)
  top.append(v)

dfs2 funksiyası tərs qraf üzərində dərinliyinə axtarış yerinə yetirir. Əsas proqramdan -nin bir çağırışı zamanı ziyarət edilən bütün təpələr bir güclü bağlı komponent təşkil edir.

def dfs2(v):
  global gr, used
  used[v] = True
  for to in gr[v]:
    if not used[to]: dfs2(to)

Proqramın əsas hissəsi. Giriş qrafını oxuyun. Hər a \to b kənarını qrafına, əks b \to a kənarını isə tərs qrafına əlavə edin.

n, m = map(int, input().split())
g = [[] for _ in range(n + 1)]
gr = [[] for _ in range(n + 1)]
for _ in range(m):
  a, b = map(int, input().split())
  g[a].append(b)
  gr[b].append(a)

Giriş qrafı üzərində hələ ziyarət edilməmiş hər təpədən dərinliyinə axtarışa başlayın. Bundan sonra siyahısı qrafın bütün təpələrini emalının başa çatma ardıcıllığı ilə ehtiva edir.

used = [False] * (n + 1)
top = []
for i in range(1, n + 1):
  if not used[i]: dfs1(i)

İkinci gəzintidən əvvəl siyahısını sıfırlayın. Tərs qrafın təpələri emalının başa çatma ardıcıllığının tərsinə baxılmalıdır, buna görə siyahısını tərsinə çevirin.

used = [False] * (n + 1)
top.reverse()

dəyişəni güclü bağlı komponentlərin sayını hesablayır.

c = 0

siyahısı üzərində soldan sağa hərəkət edin. Hələ ziyarət edilməmiş hər təpəsindən tərs qraf üzərində dfs2 dərinliyinə axtarışına başlayın. O, dəqiq bir yeni güclü bağlı komponenti gəzir, buna görə -ni artırın.

for v in top:
  if not used[v]:
    dfs2(v)
    c += 1

Qrafın güclü bağlı komponentlərinin sayını çap edin.

print(c)