Tarjan演算法_縮點

阿_波_發表於2018-03-30

我們這一篇是在已經瞭解Tarjan演算法的基礎之上開始寫的,如果不瞭解的話,請先看大牛們關於Tarjan演算法的部落格。

首先我們先看一下一個問題:一個有向圖,有n個點以及m條邊,我們至少應該新增幾條邊才能使整個圖變成強連通圖。或者是一個無向圖至少新增幾條邊變成連通圖。

首先我們對於一個有向無環的圖(DAG),至少新增幾條邊才能使它變為強連通圖?我們很容易根據有向無環圖的性質得到,我們計算入度為零的點數為a,出度為零的點數為b,那麼我們至少需要新增的邊數為max(a,b),如果只有一個點的話,我們不需要新增任何邊。

那麼我們怎麼把一個圖轉換為DAG呢,因為上面給出的圖可能存在環,那麼我們就會想到把已經組成全連通的子圖轉換成一個點來看,那麼我們最終的圖就不會包含環了。

好了,解決這類問題的思路已經想好了,下面我們來進行求解:

        我們使用Tarjan演算法求解出強連通分量之後,我們使用一個belong陣列將同一個連通分量的點分配相同的數值,存放在belong陣列中,然後我們再次遍歷一遍點,然後這次操作的是belong陣列中對應的數值,只有把不屬於同於個連通分量的邊新增到新的圖中,並且根據這些邊來計算每個縮點的入度以及出度。

//不怕別人比你聰明,就怕別人比你聰明還比你努力
#include<iostream>
#include<string>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include <set>
#include <map>
#include<vector>
#define INF 0x3f3f3f3f

using namespace std;
const int MAXN = 1000;
struct Node
{
    int fr;
    int v;
    int next1;
}edge1[MAXN],edge2[MAXN];   //edge1表示還沒有縮點之前的圖,edge2表示縮點之後的圖的連通關係
int head[MAXN];
int dfn[MAXN],low[MAXN];
int vis[MAXN],stact[MAXN];
int belong[MAXN],num[MAXN];
//belong表示每個點屬於的縮完之後的哪一個點,num表示每一個縮點裡面有多少個點
int cnt,tot,index,now;

void add(int x,int y,Node* edge)
{
    edge[++cnt].next1 = head[x];
    edge[cnt].v = y;
    edge[cnt].fr = x;
    head[x] = cnt;
}

void Tarjan(int x)
{
    low[x] = dfn[x] = ++tot;
    vis[x] = 1;
    stact[++index] = x;

    for(int i = head[x];i != -1; i = edge1[i].next1)
    {
        int v = edge1[i].v;
        if(!dfn[v])
        {
            Tarjan(v);
            low[x] = min(low[x], low[v]);
        }
        else if(vis[v])
            low[x] = min(low[x], dfn[v]);
    }
    if(low[x] == dfn[x])
    {
        ++now;
        do
        {
            printf("%d ",stact[index]);
            vis[stact[index]] = 0;
            belong[stact[index]] = now;
            num[now] ++;
            index --;
        }while(x != stact[index+1]);
        printf("\n");
    }
    return ;
}

int main()
{
    memset(head,-1,sizeof(head));
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(vis,0,sizeof(vis));
    memset(stact,0,sizeof(stact));
    memset(belong,0,sizeof(belong));
    memset(num,0,sizeof(num));

    cnt = 0;tot = 0;index = 0;now = 0;
    //now最終表示縮點之後點的數目

    int n,m;
    scanf("%d%d",&n,&m);
    int x,y;
    for(int i = 1;i <= m ;i ++)
    {
        scanf("%d%d",&x,&y);
        add(x,y,edge1);
    }
    for(int i = 1;i <= n;i ++)
        if(!dfn[i])
            Tarjan(i);
    int inde[MAXN];//表示每個縮點的入讀
    int outde[MAXN];//每個縮點的出度
    //縮點完成之後,我們就一定沒有環的存在
    memset(head,-1,sizeof(head));
    int u,v;
    for(int i = 1;i <= m;i ++)
    {
        u = belong[edge1[i].fr];
        v = belong[edge1[i].v];
        //表示如果這條邊不在縮點之內,那麼就是用來連線縮點
        if(u!=v)
        {
            add(u,v,edge2);
            inde[v] ++;
            outde[u] ++;
        }
    }
    int a = 0,b = 0;
    //分別計算所的縮點中,入度和出度為0的數目
    for(int i = 1;i <= now;i ++)
    {
        if(!inde[i]) a++;
        if(!outde[i]) b++;
    }
    if(now == 1)
        //如果所有的縮點只有一個,則不需要新增新邊
        printf("0\n");
    else
        printf("%d\n",max(a,b));

}
6 8
1 3
1 2
2 4
3 4
3 5
4 6
4 1
5 6

我們的測試資料如上,答案是需要新增一條邊。

我們來看一個例題:Poj 2186

我們要想知道有多說少被全部的認為是受歡迎的,我麼先要將他們進行完縮點之後,只有其他的點都可以到達的點才是被其它都歡迎的點。

//不怕別人比你聰明,就怕別人比你聰明還比你努力
//因為和上面程式很類似,所以沒有寫註釋....
include<iostream>
#include<string>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include <set>
#include <stack>
#include <map>
#include<vector>
#define INF 0x3f3f3f3f

using namespace std;
const int MAXN =  51000;
struct Node
{
    int fr;
    int v;
    int next1;
}edge1[MAXN];
int head[MAXN];
int dfn[MAXN],low[MAXN];
int vis[MAXN],stact[MAXN];
int belong[MAXN],num[MAXN];
int cnt,tot,index,now;
int n,m;

void add(int x,int y,Node* edge)
{
    edge[++cnt].next1 = head[x];
    edge[cnt].v = y;
    edge[cnt].fr = x;
    head[x] = cnt;
}

void Tarjan(int x)
{
    low[x] = dfn[x] = ++tot;
    vis[x] = 1;
    stact[++index] = x;

    for(int i = head[x];i != -1; i = edge1[i].next1)
    {
        int v = edge1[i].v;
        if(!dfn[v])
        {
            Tarjan(v);
            low[x] = min(low[x], low[v]);
        }
        else if(vis[v])
            low[x] = min(low[x], dfn[v]);
    }
    if(low[x] == dfn[x])
    {
        ++now;
        do
        {
            vis[stact[index]] = 0;
            belong[stact[index]] = now;
            num[now] ++;
            index --;
        }while(x != stact[index+1]);
    }
    return ;
}

void Init()
{
    memset(head,-1,sizeof(head));
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(vis,0,sizeof(vis));
    memset(stact,0,sizeof(stact));
    memset(belong,0,sizeof(belong));
    memset(num,0,sizeof(num));
    cnt = 0;tot = 0;index = 0;now = 0;
    scanf("%d%d",&n,&m);
    int x,y;
    for(int i = 1;i <= m ;i ++)
    {
        scanf("%d%d",&x,&y);
        add(x,y,edge1);
    }
}
int main()
{
    Init();
    for(int i = 1;i <= n;i ++)
        if(!dfn[i])
            Tarjan(i);
    int u,v;
    int outde[MAXN] = {0};
    for(int i = 1;i <= m;i++)
    {
        u = belong[edge1[i].fr];
        v = belong[edge1[i].v];
        if(u != v)
        {
            outde[u]++;
        }
    }
    int ans = 0;
    for(int i =1;i <= now;i ++)
    {
        if(!outde[i])
        {
            if(ans > 0)
            {
                printf("0\n");
                return 0;
            }
            ans = num[i];
        }
    }
    printf("%d\n",ans);
    return 0;
}



相關文章