图强联通算法

kosaraju算法

以一个城堡问题为例:

hdu1269

题意:
为了训练小希的方向感,Gardon建立了一座大城堡,里面有N个房间(N<=10000)和M条通道(M<=100000),每个通道都是单向的,就是说若称某通道连通了A房间和B房间,只说明可以通过这个通道由A房间到达B房间,但并不说明通过它可以由B房间到达A房间。Gardon需要请你写个程序确认一下是否任意两个房间都是相互连通的,即:对于任意的i和j,至少存在一条路径可以从房间i到房间j,也存在一条路径可以从房间j到房间i。
输入输出:
输入包含多组数据,输入的第一行有两个数:N和M,接下来的M行每行有两个数a和b,表示了一条通道可以从A房间来到B房间。文件最后以两个0结束。
对于输入的每组数据,如果任意两个房间都是相互连接的,输出"Yes",否则输出"No"。
案例

3 3
1 2
2 3
3 1
3 3
1 2
2 3
3 2
0 0

思路:
ks算法是比较简单的dfs方法,很容易实现,加油!
①首先要得到一个反转边的有向图
②用反转边的图,从1结点到n结点进行dfs搜索(算法模块为rdfs),后序遍历得到一个数组!
③我们需要用到的是后序遍历数组,这个逆后序数组其实就是一个伪拓扑排序
④按照逆后序遍历数组对所有结点进行dfs搜索,结点访问过则进行标记
⑤每进行一次dfs,则就找到一个强联通分量!

#include <iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<map>
#include<set>
#include<queue>
#include<vector>
using namespace std;

vector<int> g[10005];
vector<int> rg[10005];
vector<int> vec;
int vis[10005];
int n;
void rdfs(int v)
{

    vis[v]=1;
    for(int i=0; i<rg[v].size(); i++)
    {
        if(!vis[rg[v][i]])
        {
            rdfs(rg[v][i]);
        }
    }
    vec.push_back(v);
}
int cnt;
void dfs(int v)
{
    vis[v]=1;
    for(int i=0; i<g[v].size(); i++)
    {
        if(!vis[g[v][i]])
        {
            dfs(g[v][i]);
        }
    }
}

int main()
{
    int m;
    while(cin>>n>>m)
    {
        if(n==m&&m==0)
            break;
        for(int i=1; i<=n; i++)
            g[i].clear(),rg[i].clear();
        int a,b;
        for(int i=0; i<m; i++)
        {
            cin>>a>>b;
            g[a].push_back(b);
            rg[b].push_back(a);
        }
        vec.clear();
        memset(vis,0,sizeof(vis));
        for(int i=1; i<=n; i++)
            if(!vis[i])
                rdfs(i);
        cnt=0;
        memset(vis,0,sizeof(vis));
        for(int i=n-1; i>=0; i--)
        {
            if(!vis[vec[i]])
                dfs(vec[i]),cnt++;
        }
        if(cnt==1)
            cout<<"Yes"<<endl;
        else
            cout<<"No"<<endl;

    }

    return 0;
}


Tarjan算法

poj1236

题目:N个学校,给出N个学校网络的相同关系,我们希望所有学校收到发送的消息,每个学校收到时会共享给自己单向相通的学校。问,一,至少发几个消息让所有学校都收到,二,需要构建几条单向边,让这些学校变成强连通。
思路:把强连通的学校变成一个点,得到一个DAG,在DAG上求出度入度,入度为0点的个数就是1题答案,第二题稍微有点点绕,自己画个图屡屡就清楚了,出度为0的入度为0的点的个数中取最大就是。

需要一条特判:万一得到点只有一个,说明所有学校已经强连通,则不需要加边(入出度为0皆是1)

#include <iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<map>
#include<set>
#include<queue>
#include<vector>
#include<stack>
using namespace std;
#define maxn 100005
vector<int> tu[maxn];
int dfn[maxn],low[maxn],id[maxn],in[maxn],out[maxn];
int zu[maxn];
int ans=0;
int color=0;
stack<int> cun;
int n,m;


void dfs(int x)
{
    cun.push(x);
    dfn[x]=low[x]=++ans;
    zu[x]=1;
    int v;
    for(int i=0; i<tu[x].size(); i++)
    {
        v=tu[x][i];
        if(!dfn[v])
        {
            dfs(v);
            low[x]=min(low[x],low[v]);
        }
        else if(zu[v])
        {
            low[x]=min(low[x],low[v]);
        }
    }
    int tmp;
    if(low[x]==dfn[x])
    {
        color++;
        while(cun.top()!=x)
        {
            id[cun.top()]=color;
            zu[cun.top()]=0;
            cun.pop();

        }
        zu[cun.top()]=0;
        id[cun.top()]=color;
        cun.pop();
    }

}
void tarjan()
{
    memset(dfn,0,sizeof(dfn));
    while(!cun.empty())
        cun.pop();
    memset(low,0,sizeof(low));
    for(int i=1; i<=n; i++)
    {
        if(!dfn[i])
        {
            dfs(i);
        }
    }
    for(int i=1; i<=n; i++)
    {
        for(int j=0; j<tu[i].size(); j++)
        {
            if(id[i]!=id[tu[i][j]])
            {
                in[id[tu[i][j]]]++;
                out[id[i]]++;
            }
        }
    }
    if(color==1)
    {
        cout<<1<<endl;
        cout<<0;
        return ;
    }
    int tmp1=0,tmp2=0;
    for(int i=1; i<=color; i++)
    {
        if(in[i]==0)
            tmp1++;
        if(out[i]==0)
            tmp2++;
    }
    cout<<tmp1<<endl;
    cout<<max(tmp2,tmp1);

}
int main()
{
    int x;
    cin>>n;

    for(int i=1; i<=n; i++)
    {
        tu[i].clear();
        while(cin>>x&&x!=0)
        {
            tu[i].push_back(x);
        }
    }
    tarjan();

    return 0;
}


POJ2186 tarjan+缩点

思路: 把互相认可的牛看成一个点,然后得到一个DAG,判断一下这个DAG有几个连通分量,如果只有一个,说明,有一个点是所有牛都认可的(而且点内部也互相认可),那么这个DAG里出度为0的点,这个点里有几个牛,那么就是答案了

#include <iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<map>
#include<set>
#include<queue>
#include<vector>
#include<stack>
using namespace std;
#define maxn 100005
vector<int> tu[maxn];
int dfn[maxn],low[maxn],id[maxn],in[maxn],out[maxn];
int num[maxn];
int zu[maxn];
int ans=0;
int color=0;
stack<int> cun;
int n,m;
vector<int> arr[maxn];
void dfs(int x){
    zu[x]=1;
    cun.push(x);
    dfn[x]=low[x]=++ans;
    for(int i=0;i<tu[x].size();i++){
        int v= tu[x][i];
        if(!dfn[v]){dfs(v);low[x]=min(low[x],low[v]);}else if(zu[v]){low[x]=min(low[x],low[v]);}//深搜找强联通
    }
    if(dfn[x]==low[x]){ //强连通缩点
            color++;
        while(cun.top()!=x){
            id[cun.top()]=color;
            arr[color].push_back(cun.top());
            cun.pop();

        }id[cun.top()]=color;arr[color].push_back(cun.top());cun.pop();
    }
}

int main()
{
    cin>>n>>m;
    int a,b;
    for(int i=0;i<m;i++){
        cin>>a>>b;
        tu[a].push_back(b);
    }
    for(int i=1;i<=n;i++){
        if(!dfn[i]){
            dfs(i);
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=0;j<tu[i].size();j++){
            if(id[tu[i][j]]!=id[i]){
                in[id[tu[i][j]]]++;
                out[id[i]]++;
            }
        }
    }

    int idx=0;
    for(int i=1;i<=color;i++){
        if(out[i]==0){
                if(idx==0)
                    idx=i;
                else{
                    idx=-1;
                }
        }
    }
    if(idx!=-1&&idx!=0){
        cout<<arr[idx].size()<<endl;
    }
    else {
        cout<<0<<endl;
    }

    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值