Körpülər#1943

Körpülər (Həll təhlili)

Körpü adlanan kənar, silindikdə qrafın əlaqəli komponentlərinin sayını artıran kənardır. Kənar o zaman və yalnız o zaman körpüdür ki, o, heç bir sadə dövrün üzərində yerləşməsin: əgər kənar dövrə aiddirsə, onda silindikdən sonra kənarın uc nöqtələri dövrün qalan hissəsi vasitəsilə əlaqəli qalır.

Dərinliyinə axtarışı işə salaq. Qrafın hər bir kənarı ya axtarış ağacının kənarı, ya da əks kənar olacaq. Əks kənar onun uc nöqtələri arasındakı ağac üzrə yol ilə birlikdə dövr əmələ gətirir, buna görə də o, körpü ola bilməz. Deməli, körpü yalnız axtarış ağacının kənarları ola bilər.

Axtarış zamanı hər bir təpəsi üçün iki etiket hesablayaq:

(v, to) ağac kənarına baxaq, burada — təpəsinin oğludur. Üç hal mümkündür:

  1. up[to] < d[v]. alt ağacının hansısa təpəsindən təpəsinin öz əcdadına əks kənar gedir. Başqa sözlə, təpəsindən və ya onun törəməsindən -nin əcdadına əks kənar o zaman və yalnız o zaman mövcuddur ki, -nin elə oğlu tapılsın ki, up[to] < d[v] olsun.

  2. up[to] = d[v]. alt ağacı məhz təpəsinə gedən əks kənar saxlayır.

  3. up[to] > d[v]. alt ağacının heç bir təpəsi (v, to) kənarından keçmədən təpəsinə və ya onun əcdadlarına çata bilmir. Deməli, (v, to) kənarı körpüdür.

İlk iki halda (v, to) kənarı dövrün üzərində yerləşir və körpü deyil. Beləliklə, aşağıdakı meyarı alırıq:

Teorem. Axtarış ağacının (v, to) kənarı, burada — təpəsinin oğludur, o zaman və yalnız o zaman körpüdür ki, up[to] > d[v] olsun.

Dərinliyinə axtarış O(n + m) vaxtında yerinə yetirilir. Tapılmış körpülərin nömrələri çıxışdan əvvəl sıralanır.

Misal

Misalda verilmiş qraf aşağıdakı görünüşə malikdir. Hər bir kənarın yanında onun giriş məlumatlarındakı nömrəsi göstərilib:

təpəsindən dərinliyinə axtarışı işə salaq. Hər bir təpənin qonşularını giriş məlumatlarında kənarların görünmə sırası ilə nəzərdən keçirəcəyik. Gəzinti təpələri 1, 2, 3, 4, 5, 6 sırası ilə ziyarət edir. Axtarış ağacının kənarları 1, 2, 3, 5 və nömrəli kənarlar olur, 4 = (3, 1) və 6 = (6, 4) kənarları isə əks kənarlar olur.

Ağacın bütün kənarları üçün up[to] > d[v] şərtini yoxlayaq:

Kənar (v, to)

Nömrə

Nəticə

1

1

1

up[2] = d[1], körpü deyil

2

1

2

up[3] < d[2], körpü deyil

3

4

3

up[4] > d[3], körpü

5

4

4

up[5] = d[4], körpü deyil

7

4

5

up[6] < d[5], körpü deyil

Beləliklə, qrafda düz bir körpü var — və təpələrini birləşdirən nömrəli kənar.

Alqoritmin reallaşdırılması

Qrafı qonşuluq siyahısı şəklində saxlayırıq. Hər bir təpəsi üçün -də (to, id) cütləri yazılır, burada — qonşu təpə, isə (v, to) kənarının giriş məlumatlarındakı nömrəsidir. Kənarın nömrəsi iki səbəbə görə lazımdır: gəzinti zamanı gəldiyimiz eyni kənarla geri qayıtmamaq üçün və körpüləri təpə cütləri kimi deyil, giriş kənarlarının nömrələri kimi çıxarmaq üçün.

və massivləri və etiketlərini saxlayır. -nin sıfır qiyməti təpəsinin hələ ziyarət edilmədiyini bildirir. bridges massivində körpü olan kənarların nömrələrini toplayırıq.

vector<vector<pair<int, int> > > g;
vector<int> d, up, bridges;

dfs funksiyası təpəsindən dərinliyinə axtarışı reallaşdırır. parametri — içinə təpəsinə gəldiyimiz kənarın nömrəsidir. Axtarış ağacının kökü üçün belə kənar olmadığından, defolt olaraq p = -1 götürülür. Təpəyə girişdə və -yə cari vaxtı təyin edirik.

void dfs(int v, int p = -1)
{
  d[v] = up[v] = timer++;

təpəsindən çıxan bütün (v, to) kənarlarını nəzərdən keçiririk. nömrəli kənarı ötürürük: biz bu kənarla -yə gəlmişik, və o, valideynə aparır.

  for (auto edge : g[v])
  {
    int to = edge.first;
    int id = edge.second;
    if (id == p) continue;

Əgər təpəsi artıq ziyarət edilibsə (d[to] \ne 0), onda (v, to) — əks kənardır. -ni -ya giriş vaxtını nəzərə alaraq yenidən hesablayırıq.

    if (d[to] != 0)
      up[v] = min(up[v], d[to]);

Əks halda (v, to) — ağac kənarıdır. kənarının nömrəsini ötürərək təpəsindən axtarışı işə salırıq. Geri qayıtdıqdan sonra -ni vasitəsilə yenidən hesablayırıq.

    else
    {
      dfs(to, id);
      up[v] = min(up[v], up[to]);

Əgər up[to] > d[v] olarsa, onda alt ağacından (v, to) kənarından keçmədən -yə və ya onun əcdadlarına düşmək mümkün deyil, buna görə də nömrəli kənar körpüdür.

      if (up[to] > d[v]) bridges.push_back(id);
    }
  }
}

Proqramın əsas hissəsi. Giriş qrafını oxuyuruq. Hər bir kənarını onun nömrəsi ilə birlikdə hər iki təpənin qonşuluq siyahısına əlavə edirik.

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

Etiket massivlərini işə salırıq.

d.resize(n + 1);
up.resize(n + 1);

dəyişəni axtarışın cari vaxtını saxlayır.

timer = 1;

Qraf əlaqəli olmaya bilər, buna görə də hələ ziyarət edilməmiş hər bir təpədən dərinliyinə axtarışı işə salırıq.

for (i = 1; i <= n; i++)
  if (d[i] == 0) dfs(i);

Körpülərin nömrələrini artan sıra ilə sıralayırıq.

sort(bridges.begin(), bridges.end());

Körpülərin sayını, növbəti sətirdə isə onların nömrələrini çıxarırıq.

printf("%d\n", bridges.size());
for (i = 0; i < bridges.size(); i++)
  printf("%d ", bridges[i]);
printf("\n");