HDU2767——Tarjan+缩点

HDU2767
题目的意思很简单,给定你一个有向图,问最少加几条边可以使得整个图变为强连通图。
[任意两点之间连通]

思路:
1.先跑一次Tarjan将这个有向图分割成不同的连通子图。连通子图的数目用color标记,即我们用染色的方式区分不同的连通子图。

2.Tarjan算法中,对color进行更新时对连通分量缩点。所谓的缩点,其实就是给每个节点做一个标记,标记该点属于哪个连通分量,然后整个连通分量就相当于一个点了。我们创建belong数组,其中belong[v] = color表示节点v被染成了color色(也就是说v点属于color这个连通分量)。

3.通过缩点,整个有向图可以看做是一个有向无环图(DAG),不难想象,任何一个DAG都有至少一个入度为0的节点v1,也至少存在一个出度为0的节点v2。然后,我们记录每一个连通分量的所有出度为0或者入度为0的节点的数目。

4.最后记录所有连通分量的入度为0的数目cnt1和出度为0的数目cnt2,cnt1和cnt2两者之间的最大值就是我们要求的添加最少的边的数目。
[原理我也并不清楚…]
代码:

/*HDU 2767
  Judge Status : Accepted
  Time: 296MS
  不知道为什么如果stack<int> s放在局部函数Tarjan里边就会Runtime Error(STACK_OVERFLOW)
*/
#include <iostream>
#include <cstdio>
#include <vector>
#include <stack>
#include <cstring>
using namespace std;
const int maxn = 20005;

vector<int> edge[maxn];
stack<int> s;//为什么要放在外边啊!
int dfn[maxn],low[maxn],belong[maxn];
int indegree[maxn],outdegree[maxn];
bool instack[maxn];
int time,color;

int Min(int x,int y) { return (x < y) ? x : y ;}
int Max(int x,int y) { return (x > y) ? x : y ;}

void init(int n)
{
    time = 0;//时间戳
    color = 0;//强连通分量的个数,给不同的连通分量染上不同的颜色进行区分
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(instack,false,sizeof(instack));
    memset(belong,0,sizeof(belong));
    for(int i = 0 ; i <= n ; ++i)
        edge[i].clear();
}
void tarjan(int v1)
{
    int v2;

    dfn[v1] = low[v1] = ++time;//赋予初始值
    s.push(v1);
    instack[v1] = true;

    for(unsigned i = 0 ; i < edge[v1].size() ; ++i){
        v2 = edge[v1][i];//与v1向连的点
        if(dfn[v2] == 0){
            tarjan(v2);
            low[v1] = Min(low[v1],low[v2]);
        }
        else if(instack[v2] && dfn[v2] < low[v1])//如果要搜素的点v2已经在栈中
            low[v1] = dfn[v2];
    }
    if(dfn[v1] == low[v1]){//找到连通分量
        color++;
        do{
            v2 = s.top();
            s.pop();
            instack[v2] = false;
            belong[v2] = color;//染色过程实际上就是缩点的过程
        }
        while(v1 != v2);
    }
}
void solve(int n,int m)
{
    if(color == 1){//如果只有一个联通分量,说明这是强连通图,不需要添加点
        printf("0\n");
        return ;
    }
    //统计每一个联通分量中的出入度之和
    //因为每一个联通分量“缩成”了一个点,求联通分量的出入度之和实际上就是求这个“缩点”的出度和入度
    for(int i = 0 ; i <= color ; ++i)
            indegree[i] = outdegree[i]  = 0;
    for(int v1 = 1 ; v1 <= n ; ++v1){
        for(unsigned i = 0 ; i < edge[v1].size() ; ++i){
            int v2 = edge[v1][i];
            if(belong[v1] != belong[v2]){//如果说处于不同的连通分量
                outdegree[belong[v1]]++;求该联通分量的出度之和
                indegree[belong[v2]]++;//求该联通分量的入度之和
            }
        }
    }
    //统计每一个“缩点”入度为0或者出度为0的数目求其最大值
    int cnt1 = 0 ,cnt2 = 0;
    for(int i = 1 ; i <= color ; ++i){
        if(indegree[i] == 0)
            cnt1++;
        if(outdegree[i] == 0)
            cnt2++;
    }
    printf("%d\n",Max(cnt1,cnt2));
}
int main()
{
    int test;
    int n,m;
    int v1,v2;

    scanf("%d",&test);
    while(test--){
        scanf("%d%d",&n,&m);
        init(n);
        for(int i = 0 ;i < m ; ++i){
            scanf("%d%d",&v1,&v2);
            edge[v1].push_back(v2);
        }
        for(int i = 1 ; i <= n ; ++i)
            if(dfn[i] == 0)//如果还没有被访问过
                tarjan(i);// 1 <= n <= maxn
        solve(n,m);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值