程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> [poj 2553]The Bottom of a Graph[Tarjan強連通分量]

[poj 2553]The Bottom of a Graph[Tarjan強連通分量]

編輯:C++入門知識

題意:

求出度為0的強連通分量.

思路:

縮點

具體有兩種實現:

1.遍歷所有邊, 邊的兩端點不在同一強連通分量的話, 將出發點所在強連通分量出度+1.

 

#include <cstdio>
#include <cstring>
#include <stack>
#include <algorithm>
using namespace std;
//0.03s  4856K
const int MAXN = 5005;
struct Pool
{
    int pre, v;
}p[MAXN*100];//適當開
int num,head[MAXN];
int low[MAXN];
int dfn[MAXN],Index;
int id[MAXN],size;
bool vis[MAXN];
stack<int> s;
int n,m;
int deg[MAXN];

void clear()
{
    num = 1;//求鄰邊,異或方便,從2開始
    memset(head,0,sizeof(head));
    memset(vis,false,sizeof(vis));
    memset(low,0,sizeof(low));
    memset(dfn,0,sizeof(dfn));
    memset(deg,0,sizeof(deg));
    Index = size = 0;
    while(!s.empty())   s.pop();
}

void add(int u, int v)
{
    p[++num].v = v;
    p[num].pre = head[u];//pre為0,說明該邊為第一條邊
    head[u] = num;
}

void Tarjan(int u)
{
    dfn[u] = low[u] = ++Index;
    s.push(u);
    vis[u] = true;
    for(int tmp = head[u],k;k = p[tmp].v,tmp; tmp = p[tmp].pre)
    {
        if(!dfn[k])
        {
            Tarjan(k);
            low[u] = min(low[u], low[k]);
        }
        else if(vis[k])
        {
            low[u] = min(low[u], low[k]);
            ///low[u] = min(low[u], dfn[k]);這兩種都可以啦~
        }
    }
    
    if(dfn[u]==low[u])
    {
        size++;
        int k;
        do
        {
            k = s.top(); s.pop();
            vis[k] = false;
            id[k] = size;
        }while(k!=u);
    }
}

void cal()
{
    for(int i=1;i<=n;i++)
    {
        for(int tmp = head[i],k;k = p[tmp].v,tmp; tmp = p[tmp].pre)
        {
            if(id[i]!=id[k])
            {
                deg[id[i]]++;
            }
        }
    }
}

int main()
{
    while(scanf("%d",&n),n)
    {
        clear();
        scanf("%d",&m);
        for(int i=0,u,v;i<m;i++)
        {
            scanf("%d %d",&u,&v);
            add(u,v);
        }
        for(int i=1;i<=n;i++)
        {
            if(!dfn[i])
                Tarjan(i);
        }
        cal();
        bool blank = false;
        for(int i=1;i<=n;i++)
        {
            if(!deg[id[i]])
            {
                if(!blank)
                {
                    printf("%d",i);
                    blank = true;
                }
                else
                    printf(" %d",i);
            }
        }
        printf("\n");
    }
}


2. 在dfs的過程中,標記出度.

設當前節點為u


若訪問到了黑色點, 則出度不為0.


若訪問到了灰色點, 正常

若訪問到了白色點, 則這個白色點k

若被搜索之後屬於同一強連通分量,則low[ k ] < dfn[ k ] (注意,並不一定有 low[ k ] < low[ u ], 因為k可能連接到了較靠後的灰色點,而u之前已經被較靠前的灰色點更新過).

若被搜索之後屬於另一個(不同於u的)強連通分量, 那麼可以證明 low[ k ] == dfn[ k ], 即k一定是入口.

黑體字的兩條就包括了所有出度非0的情況. 據此來實現縮點.

 

#include <cstdio>
#include <cstring>
#include <stack>
#include <algorithm>
using namespace std;
//0.03s   4812K
const int MAXN = 5005;
struct Pool
{
    int pre, v;
}p[MAXN*100];//適當開
int num,head[MAXN];
int low[MAXN];
int dfn[MAXN],Index;
int id[MAXN],size;
bool vis[MAXN];
stack<int> s;
int n,m;
bool black[MAXN];
bool odd[MAXN];


void clear()
{
    num = 1;
    memset(head,0,sizeof(head));
    memset(vis,false,sizeof(vis));
    memset(low,0,sizeof(low));
    memset(dfn,0,sizeof(dfn));
    memset(black,false,sizeof(black));
    memset(odd,false,sizeof(odd));
    Index = size = 0;
    while(!s.empty())   s.pop();
}

void add(int u, int v)
{
    p[++num].v = v;
    p[num].pre = head[u];
    head[u] = num;
}

void Tarjan(int u)
{
    dfn[u] = low[u] = ++Index;
    s.push(u);
    vis[u] = true;
    for(int tmp = head[u],k;k = p[tmp].v,tmp; tmp = p[tmp].pre)
    {
        if(!dfn[k])
        {
            Tarjan(k);
            if(low[k]==dfn[k])///如果訪問到了白色點,那麼新的強連通分量的入口一定在這個點
                black[u] = true;
            low[u] = min(low[u], low[k]);
        }
        else if(vis[k])
        {
            low[u] = min(low[u], low[k]);
        }
        else
            black[u] = true;
    }///low只是指"當前找到的強連通分量的進入時間戳"
    ///而非"極大強連通分量"的進入時間戳.但是肯定小於自己的時間戳(恰好是進入點的話就是等於).
    if(dfn[u]==low[u])
    {
        size++;
        int k;
        do
        {
            k = s.top(); s.pop();
            vis[k] = false;
            id[k] = size;
            if(black[k])
                odd[size] = true;
        }while(k!=u);
    }
}

int main()
{
    while(scanf("%d",&n),n)
    {
        clear();
        scanf("%d",&m);
        for(int i=0,u,v;i<m;i++)
        {
            scanf("%d %d",&u,&v);
            add(u,v);
        }
        for(int i=1;i<=n;i++)
        {
            if(!dfn[i])
                Tarjan(i);
        }
        bool blank = false;
        for(int i=1;i<=n;i++)
        {
            if(!odd[id[i]])
            {
                if(!blank)
                {
                    printf("%d",i);
                    blank = true;
                }
                else
                    printf(" %d",i);
            }
        }
        printf("\n");
    }
}

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved