1. 程式人生 > >tarjan學習(復習)筆記

tarjan學習(復習)筆記

直接 class radi rst 圖論 spfa lin -- 套路

題目背景

縮點+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學習(復習)筆記