天天看點

hiho一下 連通性·三 強連通分量

時間限制: 10000ms 單點時限: 1000ms 記憶體限制: 256MB

描述

暑假到了!!小Hi和小Ho為了體驗生活,來到了住在大草原的約翰家。今天一大早,約翰因為有事要出去,就拜托小Hi和小Ho忙幫放牧。

約翰家一共有N個草場,每個草場有容量為W[i]的牧草,N個草場之間有M條單向的路徑。

小Hi和小Ho需要将牛羊群趕到草場上,當他們吃完一個草場牧草後,繼續前往其他草場。當沒有可以到達的草場或是能夠到達的草場都已經被吃光了之後,小hi和小Ho就把牛羊群趕回家。

一開始小Hi和小Ho在1号草場,在回家之前,牛羊群最多能吃掉多少牧草?

舉個例子:

hiho一下 連通性·三 強連通分量

圖中每個點表示一個草場,上部分數字表示編号,下部分表示草場的牧草數量w。

在1吃完草之後,小Hi和小Ho可以選擇把牛羊群趕到2或者3,假設小Hi和小Ho把牛羊群趕到2:

吃完草場2之後,隻能到草場4,當4吃完後沒有可以到達的草場,是以小Hi和小Ho就把牛羊群趕回家。

若選擇從1到3,則可以到達5,6:

選擇5的話,吃完之後隻能直接回家。若選擇6,還可以再通過6回到3,再到5。

是以該圖可以選擇的路線有3條:

1->2->4 		total: 11
1->3->5 		total: 9
1->3->6->3->5: 		total: 13
        

是以最多能夠吃到的牧草數量為13。

本題改編自USACO月賽金組

輸入

第1行:2個正整數,N,M。表示點的數量N,邊的數量M。1≤N≤20,000, 1≤M≤100,000

第2行:N個正整數,第i個整數表示第i個牧場的草量w[i]。1≤w[i]≤100,000

第3..M+2行:2個正整數,u,v。表示存在一條從u到v的單向路徑。1≤u,v≤N

輸出

第1行:1個整數,最多能夠吃到的牧草數量。

樣例輸入

6 6
2 4 3 5 4 4
1 2
2 4
1 3
3 5
3 6
6 3      
樣例輸出
13      

提示:強連通分量

小Ho:我覺得這次最大的問題就是會出現環了。

小Hi:假如沒有環呢?

小Ho:那樣我就可以這麼來做:

因為不存在環,那麼圖就滿足了有向無環的性質。我可以利用上次我們解決病毒問題時同樣的思路,将累加改為求最大值,利用拓撲排序計算出從起點到目前節點能夠得到的最大牧草數量。即:

假設gress[i]表示從起點到草場i累計可以吃到的牧草數量。

grass[i] = max{ grass[j] | j 是 i的前驅結點 } + w[i]      

最後我隻需要知道最大的grass[i],就是我們所要尋求的答案。

小Hi:小Ho很厲害嘛,這麼快就學會了使用拓撲排序。

小Ho:哎嘿嘿....

小Hi:你說的沒錯,但是這次的問題最大的限制就是有環的存在。

小Ho:是以有什麼辦法能夠處理麼?

小Hi:當然有了,不過首先我們要了解一個新的知識——強連通分量,其定義為:

對于有向圖上的2個點a,b,若存在一條從a到b的路徑,也存在一條從b到a的路徑,那麼稱a,b是強連通的。
對于有向圖上的一個子圖,若子圖内任意點對(a,b)都滿足強連通,則稱該子圖為強連通子圖。
非強連通圖有向圖的極大強連通子圖,稱為強連通分量。
		      

特别地,和任何一個點都不強連通的單個點也是一個強連通分量。

對于一個強連通的分量,也就是在其中任意一個點都可以到達其他點。如果草場滿足這個性質呢?

小Ho:如果草場是一個強連通圖,那麼我們隻要走到任意一點,就可以把其他所有的草場都走一遍,并且可以選擇任意一個點作為終點。

小Hi:沒錯,再回頭看看我們的例子:

hiho一下 連通性·三 強連通分量

同時存在邊(3,6)和(6,3),是以3和6這兩個節點也就構成了一個強連通分量。在我們路徑中也就存在了3->6->3這樣一段。如果換個方式講的話:

首先到了3,在這個強連通分量中,我們将該分量中所有的點都走過了之後,又重新回到了3。

在處理的時候,我們不妨可以将6和3縮成一個點,那麼就可以得到:

hiho一下 連通性·三 強連通分量

這樣是不是就變成了你可以解決的問題了?

小Ho:是的,而且答案也沒有變化。也就是說我需要滿足強連通的點都縮在一起就好了,但是我怎麼确定應該縮到哪一點呢?

小Hi:因為一個強連通塊會被縮成一個點,那麼我們可以直接建立一個新的圖,這個圖中的點對應的是每一個強連通塊。若原圖中存在邊(u,v),連接配接了屬于強連通分量A的點u和屬于強連通分量B的點v,那麼我們就在新圖中建立一條邊(A,B)。

小Ho:哦,我大概明白了。這次的問題處理方法就是先根據原圖找到所有的強連通分量,然後再根據原圖邊的關系建立新的圖,之後再用拓撲排序來處理就可以得到最終結果。

小Hi:沒錯,就是這樣。

小Ho:那麼我還有最後一個問題:我們要如何求強連通分量。

小Hi:當然,我們還是得用Tarjan算法來完成了,其僞代碼如下:

tarjan(u)
{
    Dfn[u]=Low[u]=++Index                      // 為節點u設定次序編号和Low初值
    Stack.push(u)                              // 将節點u壓入棧中
    for each (u, v) in E                       // 枚舉每一條邊
        if (v is not visted)                   // 如果節點v未被通路過
            tarjan(v)                          // 繼續向下找
            Low[u] = min(Low[u], Low[v])
        else if (v in Stack)                   // 如果節點v還在棧内(很重要,無向圖沒有這一步)
            Low[u] = min(Low[u], Dfn[v])
    if (Dfn[u] == Low[u])                      // 如果節點u是強連通分量的根
        repeat
            v = Stack.pop                      // 将v退棧,為該強連通分量中一個頂點
            mark v                             // 标記v,同樣通過棧來找連通分量
        until (u == v)
}
		      

小Ho:我懂了。我這就去實作它!

小Hi:當然,别忘了還有最重要的一個條件,我們開始在草場1,是以可能有些草場我們是無法到達的,那樣的草場需要特殊處理,簡單的做法就是将草量處理為0。

小Ho:好,我知道了!

#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <set>
#include <queue>
#include <algorithm>
using namespace std;
#define maxn 20100 
#define maxe 200100
struct node
{
    int v;
    int next;
}Edge[maxe],Edge2[maxe];

int dfn[maxn],low[maxn],stack[maxn],head[maxe],head2[maxe],w[maxn],belong[maxn],n,m,cnt,Index,top,cnt2;
int inDeg[maxn]; //各節點的入度
long long ans,num[maxn],ans2[maxn];
bool instack[maxn],vis[maxn][maxn],vis2[maxn];
set<int>Set;
void AddEdge(int a,int b,node *Edge,int *head)
{
    Edge[cnt].v=b;
    Edge[cnt].next=head[a];
    head[a]=cnt++;
}
void topoSort();
void tarjan(int u)
{
    int v;
    dfn[u]=low[u]=++Index; // 為節點u設定次序編号和Low初值
    stack[++top]=u;         // 将節點u壓入棧中
    instack[u]=true;
    for (int i=head[u]; i!=-1; i=Edge[i].next) // 枚舉每一條邊
    {
        v=Edge[i].v;
        if(!dfn[v])    // 如果節點v未被通路過
        {
            tarjan(v); // 繼續向下找
            low[u]=min(low[u],low[v]);
            
        }
        else if(instack[v])  // 如果節點v還在棧内(很重要,無向圖沒有這一步)
        {
            low[u]=min(low[u],dfn[v]);
        }
    }
    if (dfn[u]==low[u]) // 如果節點u是強連通分量的根
    {
        cnt2++;
        int j;
        do
        {
            j=stack[top--];  // 将v退棧,為該強連通分量中一個頂點
            belong[j]=cnt2;  //結點j屬于新結點cnt2
            instack[j]=false; //重置狀态
            
        }while (j!=u);
    }
}
void dfs(int u, long long w)
{
    if(ans<w)
    {
        ans=w;
    }
    for (int i=head2[u]; i!=-1; i=Edge2[i].next)
    {
        dfs(Edge2[i].v, w+num[Edge2[i].v]);
    }
}

void ReBuild()
{
    int v;
    cnt=0;
    for (int i=1; i<=n; i++)
    {
        num[belong[i]]+=w[i]; //更新縮點草的數量
        if(vis2[i])
        {
            Set.insert(belong[i]); //将新結點存入集合中,後面會用
        }
    }
    //重建立圖
    for (int u=1; u<=n; u++)
    {
        for (int i=head[u]; i!=-1; i=Edge[i].next)
        {
            v=Edge[i].v;
            if(belong[u]!=belong[v] && !vis[belong[u]][belong[v]])
            {
                AddEdge(belong[u], belong[v], Edge2, head2);
                vis[belong[u]][belong[v]]=1;
                inDeg[belong[v]]++;
                
            }
        }
    }
     ans=0;
     dfs(belong[1], num[belong[1]]);
     printf("%lld\n",ans);
   // topoSort();   //使用拓撲排序始終不對
}
void Init() //初始化
{
    cnt=0;
    Index=0;
    top=-1;
    memset(head, -1, sizeof(head));
    memset(head2, -1, sizeof(head2));
    for (int i=0; i<=n; i++)
    {
        dfn[i]=0;
        num[i]=0;
        belong[i]=0;
        instack[i]=false;
        inDeg[i]=0;
        ans2[i]=0;
        vis2[i]=false;
    }
    for (int i=0; i<=n; i++)
    {
        for (int j=0; j<=n; j++)
        {
            vis[i][j]=0;
        }
    }
    
}

//拓撲排序
void topoSort()
{
    queue<int>q;
    
    for (set<int>::iterator it=Set.begin(); it!=Set.end(); ++it)
    {
        ans2[*it]=num[*it];
        if(inDeg[*it]==0)
        {
            q.push(*it);
        }
    }
    int u,v;
    while (!q.empty())
    {
        u=q.front();
        q.pop();
        for (int i=head2[u]; i!=-1; i=Edge2[i].next)
        {
            v=Edge2[i].v;
            inDeg[v]--;
            ans2[v]+=ans2[u];
            if (inDeg[v]==0)
            {
                q.push(v);
            }
        }
    }
    long long ans3=0;
    for (set<int>::iterator it=Set.begin(); it!=Set.end(); ++it)
    {
        ans3=max(ans3,ans2[*it]);
    }
    printf("%lld\n",ans3);
}

void dfs2(int u) //連通性判斷
{
    vis2[u]=true;
    for (int i=head[u]; i!=-1; i=Edge[i].next)
    {
        if(!vis2[Edge[i].v])
            dfs2(Edge[i].v);
    }
}
int main()
{
    int u,v;
    Init();
    scanf("%d%d",&n,&m);
    for (int i=1; i<=n; i++)
    {
        scanf("%d",&w[i]);
    }
    for (int i=0; i<m; i++)
    {
        scanf("%d%d",&u,&v);
        AddEdge(u,v,Edge,head);
    }
    dfs2(1); //檢查圖的連通性
    for (int i=1; i<=n; i++)
    {
        if (!vis2[i]) //将和結點1沒有連通的點的權值置0
        {
            w[i]=0;
        }
    }
    tarjan(1);
    ReBuild();
    return 0;
}
           

使用dfs計算最後結果的是對的,但是用拓撲排序始終不對,這個還待解決。