1. 程式人生 > >牛客練習賽32 D Tarjan無向圖求橋+並查集維護

牛客練習賽32 D Tarjan無向圖求橋+並查集維護

題目描述:

小p和他的朋友約定好去遊樂場遊玩,但是他們到了遊樂場後卻互相找不到對方了。
遊樂場可以看做是一張n個點,m條道路的圖,每條道路有邊權wi,表示第一次經過該道路時的花費(第二次及以後經過時花費為0)。
現在,小p要去找他的朋友,但他的朋友行蹤很詭異,小p總是要遍歷完這n個點才能找到他,同時小p希望總花費最小。
找到朋友的方案可能不唯一(具體看樣例解釋),小p想知道在這所有的方案中,有多少條邊在每個方案中都會被經過。
 

輸入描述:

第一行兩個整數n, m. p,分別表示點數,邊數,小p的初始位置。
接下來m行,每行兩個整數u, v, w表示從u到v有一條無向邊,邊權為w。

輸出描述:

輸出一個整數k,表示必須經過的邊的數量。

示例1

輸入

5 7 1
1 2 3
2 3 7
1 3 5
2 4 2
1 5 3
5 4 3
2 5 3

輸出

2

樣例解釋:

 


幾種可能的方案如下:




可以證明,4 - 2和1 - 3這兩條邊在所有方案中都被經過。
(以上每種方案的總花費均為13,同時可以證明沒有比這更優的策略)

示例2

輸入

3 3 1
1 2 1
1 3 1
2 3 2

輸出

2

示例3

輸入

3 3 1
1 2 2
2 3 2
1 3 2

輸出

0

備註:

保證圖聯通,保證無自環,保證無重邊

連結:https://ac.nowcoder.com/acm/contest/272/D
來源:牛客網

簡單來說就是尋找有幾條邊一定在最小生成樹上

將路徑按照權值排序後,按照權值由小到大的順序將點增加到最小生成樹中,使用並查集維護圖的連通性

如果路徑權值相等,路徑兩個端點已經在連通圖中,那麼不用新增這條邊

如果兩個端點不在連通圖中,那麼將邊新增到新圖中,然後使用Tarjan演算法求解出無向圖中的橋,即一定存在於最小生成樹中的邊

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <vector>

using namespace std;
typedef long long ll;
const int maxn = 200100;
int n,m,s;
vector<pair<int,int> > v[maxn];      //v[x] 表示以x開始的節點,pair.first表示邊到達的節點,pair.second表示邊的編號
struct Edge
{
    int u,v,w,id;
    bool operator<(const Edge&a)const
    {
        return w < a.w;
    }
}edge[maxn];
int dfn[maxn],low[maxn],k,per[maxn],ans[maxn];

void tarjan(int x,int fa)          //Tarjan求解圖中的橋
{
    dfn[x] = low[x] = ++k;
    for(int i = 0,to,id;i < v[x].size();i ++)
    {
        if((id = v[x][i].second) == fa) continue;        //路徑返回了回去,不用考慮
        to = v[x][i].first;
        if(!dfn[to]){
            tarjan(to,id);low[x] = min(low[x],low[to]);
            if(low[to] > dfn[x]) ans[id] = 1,cout<<edge[id].u<<" "<<edge[id].v<<endl;;
        }
        else
            low[x] = min(dfn[to],low[x]);
    }
}
int find(int x)
{
    return x == per[x]?per[x]:per[x] = find(per[x]);
}

int main()
{
    int x,y,z;
    scanf("%d%d%d",&n,&m,&s);
    for(int i = 1; i <= n;i ++) per[i] = i;
    for(int i = 1;i <= m;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        edge[i] = (Edge){x,y,z,i};
    }
    sort(edge+1,edge+1+m);
    int nxt;
    for(int i = 1;i <= m;i = nxt + 1)
    {
        nxt = i+1;
        for(; edge[i].w == edge[nxt].w; nxt++);
        nxt--;             //[i-nxt]為路徑權值相等的區間範圍
        for(int j = i;j <= nxt;j ++)       //遍歷這個區間
        {
            x = find(edge[j].u),y = find(edge[j].v);
            if(x == y) continue;               //如果已經連通,則不需要考慮這條邊
            v[x].push_back(make_pair(y,j));    //否則將這條邊新增到圖中
            v[y].push_back(make_pair(x,j));
        }
        for(int j = i;j <= nxt;j ++)
        {
            x = find(edge[j].u),y = find(edge[j].v);
            if(x == y || dfn[x]) continue;
            tarjan(x,-1);
        }
        for(int j = i;j <= nxt;j ++)
        {
            x = find(edge[j].u),y = find(edge[j].v);
            if(x == y) continue;
            v[x].clear();v[y].clear();
            dfn[x] = 0;dfn[y] = 0;
            per[x] = y;
        }
    }
    int tot = 0;
    for(int i = 1;i <= m;i ++) tot += ans[i];
    printf("%d\n",tot);
    return 0;
}