1. 程式人生 > >TARJAN演算法與其運用

TARJAN演算法與其運用

一.割點與割邊

題目連結 http://hihocoder.com/problemset/problem/1183

割點:

  1. x不是根:只要有low[v[x]]>=low[x] 說明v[x]不通過x沒法回到x來時的地方,所以x為割點
  2. x是根:x有兩個及以上的直接兒子(注意這裡的兒子是放在!dfn中的,不然就是x的兒子的兒子)即可

程式碼如下:注意點都寫註釋裡了呀!

void tarjan(int x,int fa)
{
	dfn[x]=low[x]=++iindex;int child=0,flag=0;
	for(int i=0;i<v[x].size();i++)
	{
		if(!dfn[v[x][i]])
		{
			child++;//注意child的位置! 
			tarjan(v[x][i],x);	
			low[x]=min(low[x],low[v[x][i]]);
			if(low[v[x][i]]>=dfn[x]) flag=1;//割點條件 
		}
		else if(v[x][i]!=fa) low[x]=min(low[x],dfn[v[x][i]]);//這裡要判斷v[x][i]不是x的父親哦 
	}
	if(x==root&&child>=2) cut[x]=1;
	if(x!=root&&flag) cut[x]=1;
	if(cut[x]) tot++;
}

割邊:

  1. 與割點的區別:如果v[x]連爸爸都找不到了,那他到爸爸的那條邊就是連線他們的唯一路徑,就是橋(割邊)

所以這裡改成:low[v[x]]>dfn[x]

別的都一樣啊,不過不用判根

void tarjan2(int x,int fa)
{
	dfn[x]=low[x]=++iindex;
	for(int i=0;i<v[x].size();i++)
	{
		if(!dfn[v[x][i]])
		{
			tarjan2(v[x][i],x);	
			low[x]=min(low[x],low[v[x][i]]);
			if(low[v[x][i]]>dfn[x])
			{
				int t1=min(v[x][i],x);int t2=max(v[x][i],x);
				ans.push_back(make_pair(t1,t2));
			}
		}
		else if(v[x][i]!=fa) low[x]=min(low[x],dfn[v[x][i]]);
	}
}

二.邊雙

題目連結 http://hihocoder.com/problemset/problem/1184

1.就是做一遍和剛才求割點差不多的程式,但為了存點,我們引入了stack

2.stack中到x的點都屬於一個聯通分量

程式碼如下:

void tarjan(int x,int fa)
{
	dfn[x]=low[x]=++iindex;s.push(x);vis[x]=1;
	for(int i=0;i<v[x].size();i++)
	{
		if(!dfn[v[x][i]])
		{
			tarjan(v[x][i],x);
			low[x]=min(low[x],low[v[x][i]]);
		}
		else if(v[x][i]!=fa) low[x]=min(low[x],dfn[v[x][i]]);
	}
	if(low[x]==dfn[x])
	{
		tot++;int minv=1e9;
		while(1)
		{
			int tmp=s.top();minv=min(minv,tmp);
			belong[tmp]=tot;
			s.pop();	
			if(tmp==x) break;
		} 
		num[tot]=minv;
	}
}

三.強聯通分量

http://hihocoder.com/problemset/problem/1185

1.與邊雙的思路很像,但它是有向圖

2.所以引入vis的陣列以判斷這個點在不在stack裡。(而邊雙只是判斷他是不是father)

void tarjan(int x,int fa)
{
	dfn[x]=low[x]=++iindex;vis[x]=1;s.push(x);
	for(int i=0;i<v[x].size();i++)
	{
		if(!dfn[v[x][i]])
		{
			tarjan(v[x][i],x);
			low[x]=min(low[x],low[v[x][i]]);
		}
		else if(vis[v[x][i]])//這裡和邊雙不一樣 
		{
			low[x]=min(dfn[v[x][i]],low[x]);		
		}
	}
	if(dfn[x]==low[x])
	{
		while(1)
		{
			int tmp=s.top();s.pop();
			vis[tmp]=0;if(tmp!=x) w[x]+=w[tmp];
			belong[tmp]=x;
			if(tmp==x) break;
		}
	}
}

應用

1.縮點:https://www.luogu.org/problemnew/show/P3387

強聯通分量的基本應用 本質:有向有環圖->有向無環圖DAG

2.搶掠計劃:https://www.luogu.org/problemnew/show/P3627

求最長路徑,一個基本應用吶

注意topo和dfs的應用

有出發點用dfs,否則的用topo

3.資訊傳遞:https://www.luogu.org/problemnew/show/P2661

求最小環,用的強聯通分量。不過這個用dfs更清晰簡單 vis:0,1,2