POJ1236 Network of Schools(强连通分量、缩点)

原题地址

花了一晚上看了karjan,就拿这道题做一下模板题练练手吧,相关的东西都写到注释里面去了。

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stack>
using namespace std;
int n;
int cnt=0,c[110];  //cnt记录强连通分量个数,c[]记录每个点属于哪个强连通分量 
int num=0,dfn[110],low[110],ins[110]; //维护tarjan所使用的信息 
int din[110],dout[110]; //记录缩点后的图中各点的入度与出度 
stack<int> sta;

struct LIST{
    int head[110],tot,to[10010],nxt[10010];
    
    void init()
    {
        memset(head,0,sizeof(head));
        memset(to,0,sizeof(to));
        memset(nxt,0,sizeof(nxt));
        tot=0;
    }
    
    void add(int u,int v)
    {
        to[++tot]=v;nxt[tot]=head[u];head[u]=tot;
    }
}li1,li2;  //两个链表,记录缩点前后的图 

void tarjan(int u)
{
    dfn[u]=low[u]=++num;  //初始化该点的dfn与low值,dfn为到达u点的时间戳,low值为与u强连通的点集的最小时间戳 
    sta.push(u);ins[u]=1; //将点入栈,并记录其入栈状态,栈中的点是u和u的祖先 
    for(int i=li1.head[u];i;i=li1.nxt[i])
    {
        int v=li1.to[i];
        if(!dfn[v]) 
        {
            tarjan(v);  //若v还没有搜索,则先处理v 
            low[u]=min(low[u],low[v]);  //处理完v后,更新当前点的low值,因为v可能会有“后向边”,直接回到祖先点,导致low[v]更小 
        }
        else if(ins[v]) low[u]=min(low[u],dfn[v]);  //若v已经搜索过,且v在栈中,可知v是u的祖先,当然直接用其dfn[v]值更新low[u] 
    }
    if(dfn[u]==low[u])   //若当前点的时间戳等于其所处强连通分量的最小时间戳 
    {                    //说明从栈顶到该点的所有点都处于一个强连通分量 
        cnt++;           //将从栈顶到该店的所有点退栈并记录信息 
        while(1)
        {
            int x=sta.top();ins[x]=0;sta.pop();
            c[x]=cnt;    //记录该点所属的强连通分量的编号,为缩点做准备 
            if(x==u) break;
        }
    }
}

int main()
{
    li1.init();
    li2.init();
    scanf("%d",&n);
    for(int u=1,v;u<=n;u++)
        for(scanf("%d",&v);v!=0;scanf("%d",&v)) li1.add(u,v);
    for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
    //开始缩点,将新图记载li2中 
    for(int u=1;u<=n;u++)
        for(int i=li1.head[u];i;i=li1.nxt[i])
        {
            int v=li1.to[i];
            if(c[u]==c[v]) continue; 
            li2.add(c[u],c[v]); //若u和v不属于同一强连通分量,则建立这条边,顶点为c[u]和c[v] 
            dout[c[u]]++;
            din[c[v]]++; //统计每个顶点的入度和出度 
        }
    //下面是这道题的一点结论 
    if(cnt==1)  //如果这个图强连通,那么显然答案为1和0 
    {
        cout<<"1\n0\n";
        return 0;
    }
    //统计缩点后各点的入度和出度
    //最少要提供的当然是入度为0的顶点数
    //加入的关系是max(din0,dout0)
    //因为要满足条件,就需要整个图强连通,就不能有入度或出度为0的点存在
    //要消灭这些点,至少就需要max(din0,dout0)条有向边。 
    int ans1=0,ans2=0;
    for(int i=1;i<=cnt;i++) 
    {
        if(din[i]==0) ans1++;
        if(dout[i]==0) ans2++;
    }
    cout<<ans1<<endl;
    cout<<max(ans1,ans2)<<endl; 
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/BakaCirno/p/11504084.html
今日推荐