tarjan2

fervency發表於2022-02-15

反過來調過去,我還是感覺沒學明白縮點

  • 講一個有向圖中的所有強連通分量縮成一個點後,構成的新圖是一個DAG。
  • 一個點所在的強連通分量一定被該點所在DFS搜尋樹所包含
  • 樹上的邊大致分為:樹枝邊,前向邊(從上往下指),後向邊(從下往上指),橫叉變。其中前向邊肉眼可見地沒什麼卵用

接下來開始演算法流程。

  • tarjan的精髓如上次所說,在於DFS搜尋樹,在DFS搜尋樹中強連通分量以怎樣形式存在是關鍵問題。對於x,存在祖宗y,從x出發可經過橫叉邊,返祖邊,後向邊到達y,則x,y屬於同一強連通分量。操作中記錄最小 y 為:low(x)=dfn(y)。(其中單點也算強連通塊)如果有一個dfn_x=low_x ,那麼就是說 x 在一個新的強連通塊裡,同理,low_x的初始也就是dfn_x。
  • 我們用一個棧來維護 已經被遍歷過的、還未確定隸屬哪個強連通分量的 點,在該棧中越靠棧頂DFS序越靠後(是棧底元素的後代)。
  • 關於low_x的求法、更新。考慮如何求low_x:low_x 可能被更新,當且僅當x連出了一條樹枝邊,橫叉邊或後向邊。設該邊連向點 v

1.  樹枝邊: low_x= min(low_x,low_v) v 到達的點x一定可以到達,且v與x有祖宗關係
2.  後向邊: low_x= min(low_x,low_v) v 的祖先一定是 x 的祖先
3.  橫叉邊:此時分兩種情況考慮的
            當 v 點已經退棧時,那麼點v可到達的DFS序最小的祖先不是x的祖先,對 low_x 沒有貢獻;
當點v還在棧中時,v 點可到達的DFS序最小的祖先是x的祖先,有 low_x=min(low_x,low_v) (點v可到達的DFS序最小的祖先一定是x的,v 點能到達的點,x一定能到達)  特別地,由於前向邊的更新對於求強連分量沒有幫(更新是重複的),所以我們也可以有 low_x=min(low_x,low_v)

         那麼我們只需判斷點 x 連出的邊是哪一條就可以轉移了。顯然,當 dfn_v=0 時(此時v未被訪問過),這是一條樹枝邊。我們再維護一個 col 陣列, col_i 表示點 i 所在的強連通分量,在點 i 退棧時,我們對col進行賦值,那麼當 dfn_v≠0&&col_v=0 時,點v一定在棧中(後向邊指向的點一定在棧中,橫叉邊指向的點滿足此條件時在棧中,而前向邊是否存在與答案無關),此時用 low_x=min(low_x,low_v) 轉移即可,否則無需轉移。該演算法時間複雜度為(n+m),因為深度優先遍歷每個點只會經過一次,每條邊也只會訪問一次,而每個點都只會進/出棧一次,所以總時間複雜度為(n+m)

//把一個點當成根提溜出來,抖摟抖摟成一棵樹 
void dfs(int u)
{
//記錄dfs序
//可通過任意多dfs邊與最多一條非樹返祖邊到達的、本強連通分量內最小點 
	dfn[u]=low[u]=++dfs_clock;
	s.push(u);
	for(int v:g[u])
	{
		if(!dfn[v])//樹邊 
		{
			dfs(v);
			low[u]=min(low[u],low[v]);
		}
		else if(!sccnum[v])//返祖 
			low[u]=min(low[u],dfn[v]);
	}
	if(low[u]==dfn[u])
	{
		scccnt++;//強連通塊+1 
		while(1)
		{
			int x=s.top();
			s.pop();
			sccnum[x]=scccnt;
			sccsz[scccnt]++;
			if(x==u) break;
		}
	}
}

有向圖縮點

#include<bits/stdc++.h>
using namespace std;
#define N 100010
int n,m,k,tot,top,dfs_clock,ans;
int u[N],v[N],st[N],sum[N],head[N],ac[N],dfn[N],low[N],col[N],into[N],f[N];
struct tree{ int v,next; } a[N];
void add(int x,int y)
{
	a[++k].v=y;
	a[k].next=head[x];
	head[x]=k;
}

void tarjan(int x)
{
	dfn[x]=low[x]=++dfs_clock;
	st[++top]=x;
	for(int i=head[x];i;i=a[i].next)
	{
		if(!dfn[a[i].v])
		{
			tarjan(a[i].v);
			low[x]=min(low[x],low[a[i].v]);
		}
		else if(!col[a[i].v]) low[x]=min(low[x],low[a[i].v]);
	}
	if(dfn[x]==low[x])
	{
		col[x]=++tot;
		sum[tot]+=ac[x];
		while(st[top]!=x)
		{
			sum[tot]+=ac[st[top]];
			col[st[top--]]=tot;
		}
		top--;
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&ac[i]);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&u[i],&v[i]);
		add(u[i],v[i]);
	}
	for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
	memset(head,0 ,sizeof(head));
	memset(a,0,sizeof(a));
	k=0;
	for(int i=1;i<=m;i++)
	{
		if(col[u[i]]!=col[v[i]])
		{
			add(col[u[i]],col[v[i]]);
			into[col[v[i]]]++;
		}
	}
	queue<int>q;
	for(int i=1;i<=tot;i++)
	{
		f[i]=sum[i];
		if(!into[i])q.push(i);
	}
	while(!q.empty())
	{
		int y=q.front();
		q.pop();
		for(int i=head[y];i;i=a[i].next)
		{
			int x=a[i].v;
			f[x]=max(f[x],f[y]+sum[x]);
			into[x]--;
			if(!into[x]) q.push(x);
		}
	}
	for(int i=1;i<=tot;i++) ans=max(ans,f[i]);
	printf("%d",ans);
	return 0;
}