反過來調過去,我還是感覺沒學明白縮點
- 講一個有向圖中的所有強連通分量縮成一個點後,構成的新圖是一個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;
}