Oynaq nöqtələri dərinliyinə axtarış vasitəsilə tapılır. Hər bir təpəsi üçün iki etiket hesablanır:
— dərinliyinə axtarış zamanı təpəsinə giriş vaxtı;
— kökündə olan alt ağacdan bir geri qövslə getdikdə çatmaq mümkün olan təpələr arasında minimal giriş vaxtı.
Axtarış ağacının kökü olmayan təpəsi, dərinliyinə axtarış ağacında up[to] \ge d[v] bərabərsizliyi ödənən oğlu varsa, oynaq nöqtəsidir. Bu bərabərsizlik o deməkdir ki, kökündə olan alt ağacdan geri qövslər vasitəsilə təpəsindən yuxarı qalxmaq mümkün deyil. Buna görə də silindikdə bu alt ağac qrafın qalan hissəsindən kəsilmiş olacaq.
Axtarış ağacının kökü ancaq və ancaq axtarış ağacında ən azı iki oğlu olduqda oynaq nöqtəsidir.
Misal
Misaldakı qraf aşağıdakı görünüşə malikdir:
Qalın xətlərlə dərinliyinə axtarış ağacının qövsləri qeyd olunub. Hər bir təpəsinin yanında d[v] / up[v] etiketləri göstərilib. Oynaq nöqtələri rənglə seçilib.
1-ci təpə — axtarış ağacının köküdür. O, oynaq nöqtəsidir, çünki axtarış ağacında onun iki oğlu var: və təpələri.
2-ci təpə — oynaq nöqtəsidir, çünki ağac qövsü üçün up[6] \ge d[2]~(2 \ge 2) ödənilir.
3-cü təpə — oynaq nöqtəsidir, çünki ağac qövsü üçün up[8] \ge d[3]~(3 \ge 3) ödənilir.
Alqoritmin reallaşdırılması
Qraf qonşuluq siyahısı şəklində saxlanılır. massivi ziyarət edilmiş təpələri qeyd edir, və massivlərində eyniadlı etiketlər saxlanılır. dəyişəni cari dərinliyinə axtarış vaxtını saxlayır. Tapılmış oynaq nöqtələri çoxluğunda toplanır: bu çoxluq təkrarları istisna edir və təpələri artan sırada saxlayır.
vector\<vector\<int\>\> g;
vector\<int\> d, up, used;
set\<int\> ArtP;dfs funksiyası təpəsindən dərinliyinə axtarış aparır və eyni zamanda oynaq nöqtələrini tapır. parametri — təpəsinin axtarış ağacındakı atasıdır. Ağacın kökü üçün p = -1, bu susmaya görə dəyərdir.
void dfs(int v, int p = -1)
{təpəsinə girərkən onu ziyarət edilmiş kimi qeyd edirik. və etiketləri taymerin cari dəyərini alır, bundan sonra taymer artırılır.
used[v] = true;
d[v] = up[v] = timer++;children dəyişəni təpəsinin axtarış ağacındakı oğullarını sayır; bu rəqəm kök olduqda lazım olacaq.
int children = 0;ilə qonşu olan təpələrini gəzirik. (v, to) qövsü üç növdən biri ola bilər:
geriyə istiqamətdə baxılan ağac qövsü, ataya (to = p). Belə qövsü atlayırıq;
geri qövs (təpə artıq ziyarət edilib və to \ne p);
hələ ziyarət edilməmiş təpəsinə aparan ağac qövsü.
for (int to : g[v])
{
if (to == p) continue;Əgər təpəsi artıq ziyarət edilibsə, onda (v, to) — geri qövsdür. dəyərini yenidən hesablayırıq.
if (used[to])
up[v] = min(up[v], d[to]); // back edge
else
{Əks halda (v, to) — ağac qövsüdür. təpəsindən dərinliyinə axtarış başladırıq, onun atası olur. Rekursiyadan qayıtdıqdan sonra dəyərini yenidən hesablayırıq.
dfs(to, v);
up[v] = min(up[v], up[to]);Əgər up[to] \ge d[v] və təpəsi kök deyilsə (p \ne -1), onda — oynaq nöqtəsidir: kökündə olan alt ağacdan -dən yuxarı qalxmaq mümkün deyil, buna görə -nin silinməsi bu alt ağacı qrafın qalan hissəsindən kəsəcək.
if (up[to] \>= d[v] \&\& p != -1) // kök olmayan təpə
ArtP.insert(v);təpəsi — axtarış ağacında -nin növbəti oğludur, children sayğacını artırırıq.
children++;
}
}Axtarış ağacının kökü (p = -1) ancaq və ancaq onun birdən çox oğlu olduqda oynaq nöqtəsidir.
if (p == -1 \&\& children \> 1) // DFS ağacının kökü
ArtP.insert(v);
}Proqramın əsas hissəsi. Giriş məlumatlarını oxuyuruq.
scanf("%d %d", &n, &m);
g.resize(n + 1);
for (int i = 0; i \< m; i++)
{
scanf("%d %d", &a, &b);Qraf istiqamətsizdir, buna görə hər qövsünü onun hər iki ucunun qonşuluq siyahısına əlavə edirik.
g[a].push_back(b);
g[b].push_back(a);
}, və massivləri üçün yaddaş ayırırıq.
d.resize(n + 1);
up.resize(n + 1);
used.resize(n + 1);Taymeri ilkinləşdiririk və hələ ziyarət edilməmiş hər bir təpədən dərinliyinə axtarış başladırıq: qraf əlaqəli olmaya bilər.
timer = 1;
for (int i = 1; i \<= n; i++)
if (!used[i]) dfs(i);Oynaq nöqtələrinin sayını, sonra isə təpələrin özlərini çap edirik. çoxluğu sıralanmışdır, buna görə təpələr artan sırada çap olunur.
printf("%d\n", ArtP.size());
for (int v : ArtP)
printf("%d\n", v);Java reallaşdırılması
import java.util.*;
public class Main
{
static ArrayList\<Integer\>[] g;
static int used[], d[], up[];
static int time;
static TreeSet\<Integer\> ArtPoints = new TreeSet\<Integer\>();
static void dfs (int v, int p)
{
int i, to, children;
used[v] = 1;
d[v] = up[v] = time++;
children = 0;
for (i = 0; i \< g[v].size(); i++)
{
to = g[v].get(i);
if (to == p) continue;
if (used[to] == 1)
up[v] = Math.min(up[v], d[to]);
else
{
dfs (to, v);
up[v] = Math.min(up[v], up[to]);
if ((up[to] \>= d[v]) \&\& (p != -1)) ArtPoints.add(v);
children++;
}
}
if ((p == -1) \&\& (children \> 1)) ArtPoints.add(v);
}
@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]; // unchecked
used = new int[n+1];
d = new int[n+1];
up = new int[n+1];
for (int i = 0; i \<= n; i++)
g[i] = new ArrayList\<Integer\>();
for (int i = 0; i \< m; i++)
{
int a = con.nextInt();
int b = con.nextInt();
g[a].add(b);
g[b].add(a);
}
time = 1;
for(int i = 1; i \<= n; i++)
if (used[i] == 0) dfs(i,-1);
System.out.println(ArtPoints.size());
for(int i : ArtPoints)
System.out.println(i);
con.close();
}
}Python reallaşdırılması
Maksimal rekursiya dərinliyini artıraq.
import sys
sys.setrecursionlimit(10**6)Qraf qonşuluq siyahısı şəklində saxlanılır. siyahısı ziyarət edilmiş təpələri qeyd edir, və siyahılarında eyniadlı etiketlər saxlanılır. Tapılmış oynaq nöqtələri ArtPoints çoxluğunda toplanır.
g = []
used = []
d = []
up = []
ArtPoints = set()dfs funksiyası təpəsindən dərinliyinə axtarış aparır və eyni zamanda oynaq nöqtələrini tapır. parametri — təpəsinin axtarış ağacındakı atasıdır; kök üçün p = -1. children dəyişəni təpəsinin axtarış ağacındakı oğullarını sayır.
def dfs(v, p = -1):
global t
children = 0təpəsinə girərkən onu ziyarət edilmiş kimi qeyd edirik. və etiketləri -nin cari dəyərini alır, bundan sonra artırılır.
used[v] = 1
d[v] = up[v] = t
t += 1ilə qonşu olan təpələrini gəzirik. (v, to) qövsü üç növdən biri ola bilər:
geriyə istiqamətdə baxılan ağac qövsü, ataya (to = p). Belə qövsü atlayırıq;
geri qövs (təpə artıq ziyarət edilib və to \ne p);
hələ ziyarət edilməmiş təpəsinə aparan ağac qövsü.
for to in g[v]:
if to == p: continueƏgər təpəsi artıq ziyarət edilibsə, onda (v, to) — geri qövsdür. dəyərini yenidən hesablayırıq.
if used[to]:
up[v] = min(up[v], d[to])
else:Əks halda (v, to) — ağac qövsüdür. təpəsindən dərinliyinə axtarış başladırıq, onun atası olur. Rekursiyadan qayıtdıqdan sonra dəyərini yenidən hesablayırıq.
dfs(to, v)
up[v] = min(up[v], up[to])Əgər up[to] \ge d[v] və təpəsi kök deyilsə (p ≠ -1), onda — oynaq nöqtəsidir.
if up[to] \>= d[v] and p != -1:
ArtPoints.add(v)təpəsi — axtarış ağacında -nin növbəti oğludur, children sayğacını artırırıq.
children += 1Axtarış ağacının kökü (p = -1) ancaq və ancaq onun birdən çox oğlu olduqda oynaq nöqtəsidir.
if p == -1 and children \> 1:
ArtPoints.add(v)Proqramın əsas hissəsi. Siyahıları yaradırıq və giriş məlumatlarını oxuyuruq.
n, m = map(int, input().split())
g = [[] for _ in range(n + 1)]
used = [0] * (n + 1)
d = [0] * (n + 1)
up = [0] * (n + 1)İstiqamətsiz qrafın qövslərini oxuyuruq və qonşuluq siyahılarını doldururuq.
for _ in range(m):
a, b = map(int, input().split())
g[a].append(b)
g[b].append(a)Hələ ziyarət edilməmiş hər bir təpədən dərinliyinə axtarış başladırıq: qraf əlaqəli olmaya bilər.
t = 1
for i in range(1, n + 1):
if not used[i]: dfs(i)Oynaq nöqtələrinin sayını, sonra isə təpələrin özlərini artan sırada çap edirik.
print(len(ArtPoints))
for x in sorted(ArtPoints):
print(x)