tarjan學習(復習)筆記
題目背景
縮點+DP
題目描述
給定一個n個點m條邊有向圖,每個點有一個權值,求一條路徑,使路徑經過的點權值之和最大。你只需要求出這個權值和。
允許多次經過一條邊或者一個點,但是,重復經過的點,權值只計算一次。
輸入輸出格式
輸入格式:
第一行,n,m
第二行,n個整數,依次代表點權
第三至m+2行,每行兩個整數u,v,表示u->v有一條有向邊
輸出格式:
共一行,最大的點權之和。
輸入輸出樣例
輸入樣例#1: 復制2 2 1 1 1 2 2 1輸出樣例#1: 復制
2
說明
n<=10^4,m<=10^5,0<=點權<=1000
算法:Tarjan縮點+DAGdp、
基本可以算是套路了吧,先縮點,然後tpsort,跑dp,是不是可以解決不少圖論題目呢
思路:
兩個數組,dfn,low。
dfn:dfs序(時間戳)
low:以u為根的子樹裏dfn最小的那個點(它的最早祖先)
附屬數組:
st:模擬棧
co:重建圖的聯通快(點)
維護這兩個數組,當dfn[u]=low[u]時判定為強連通分量(環)
為什麽呢?
當一個點它的最老祖先等於它自己的時候,這就是一個環啊
了解四種邊:
樹枝邊:遍歷路徑
前向邊:爹——>兒
後向邊:兒——>爹
橫插邊:從這個子樹插到另外一個搜索子樹的邊
下面介紹怎麽維護low
如果(u,v)是樹枝邊,一切好說,直接比較low[u]和low[v]的最小值即可,因為v是u的兒子,直接比較它們最早祖先的大小。
如果(u,v)是後向邊或者橫插邊,就需要比較lou[u]和dfn[v]的最小值。
為什麽?
後向邊相對好理解,從這個點可以回溯到它的祖先,我們需要比較它兒子們的時間戳最小值和它祖先的時間戳 的最小值。
若之前搜到過u的祖先,那麽它祖先的dfn一定是小的,但是我能從它的耳孫之間找到它的身影(自交?回交?)!
這說明什麽?強連通分量!
但是,不要著急,我們需要找到強連通分量的根。所以我們需要比較一個極小值。
解釋通了,那麽橫插邊也是同理。
當dfn=low時:
也就是說它的子孫的最高祖先就是子孫本身時。
dfn時間戳正好是它子樹節點的low的最小值。因為dfn值具有不重復性,所以可以斷定,以它為根的子樹的所有點都是一個強連通分量。
所以,可以很好地判斷圖的環。
註意:因為圖可能不連通,所以要多次跑tarjan。
時間復雜度:由於每個點只遍歷了一次,每條邊也只遍歷了一次,所以O(N+M)(不是spfa那麽不靠譜,人家就是N+M)
給出縮點板子的代碼:
#include<bits/stdc++.h> using namespace std; const int maxn=100005; struct node { int next,to; }e[maxn]; int head[maxn],cnt,sum[maxn],a[maxn]; int n,m,ru[maxn]; inline void addedge(int from,int to) { e[++cnt].next=head[from]; e[cnt].to=to; head[from]=cnt; } int dep,top; int dfn[maxn],low[maxn],vis[maxn],co[maxn],st[maxn]; void tarjan(int u) { dfn[u]=low[u]=++dep; vis[u]=1; st[++top]=u; for(int i=head[u];i;i=e[i].next) { int v=e[i].to; if(!dfn[v]) { tarjan(v); low[u]=min(low[v],low[u]); } else if(vis[v]) { low[u]=min(low[u],dfn[v]); } } if(dfn[u]==low[u])//退棧,把強連通分量薅出來 { int t; do { t=st[top--]; sum[u]+=a[t]; co[t]=u; vis[t]=0; }while(t!=u); } } int dp[maxn]; queue < int > q; void tpsort() { for(int i=1;i<=n;i++) { if(ru[i]==0&&co[i]==i) q.push(i); dp[co[i]]=sum[co[i]]; } while(!q.empty()) { int u=q.front(); q.pop(); for(int i=head[u];i;i=e[i].next) { int v=e[i].to; dp[v]=max(dp[u]+sum[co[v]],dp[v]); if(!(--ru[v])) { q.push(v); } } } } pair < int , int > g[maxn]; int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); } for(int i=1;i<=m;i++) { int x,y; scanf("%d%d",&x,&y); addedge(x,y); g[i].first=x; g[i].second=y; } for(int i=1;i<=n;i++) { if(!dfn[i]) tarjan(i); } memset(e,0,sizeof(e)); memset(head,0,sizeof(head)); cnt=0; for(int i=1;i<=m;i++) { int x=g[i].first; int y=g[i].second; if(co[x]!=co[y]) { addedge(co[x],co[y]); ru[co[y]]++; } } tpsort(); int ans=-1; for(int i=1;i<=n;i++) { ans=max(ans,dp[i]); } printf("%d",ans); return 0; }
(完)
tarjan學習(復習)筆記