【强连通分量】POJ1236Network of Schools

题目描述:

这里写图片描述


分析

第一问很显然,把强连通分量缩成一个点之后,图中剩下多少个入度为0的点,就需要发放多少次软件(这些点没有其他点给他分享),对于入度不为0的点,就一定会从到达它的点向它分享软件。
第二问其实问的就是把一个图,通过连边的方式,缩成一个强连通分量,至少需要多少条边。那么有一个很直观的思路:对于每一个入度为0的点,必须向它连一条边,同样的,对于每一个出度为0的点,必须从它连出去一条边。设有n个入读为0的点,m个出度为0的点,至此,我们能够确定的最小连边数量就是max(n,m)条。
注:这里所说的最小连边数量并不是指只连这么多条边就能满足题意,而是说答案的最小值为max(n,m),换句话说,答案一定不小于这个值。
之后为了讲解方便,我们将入度为0的点成为A类点。
将出度为0的点成为B类点。

接下来,我们需要找一种合理的连图方式,使得连的边数刚好为max(n,m)条。首先,考虑简单一点的情况,假设n=m,很容易想到,
对任意一个B类点i,向一个无法达到i的A类点j相连接,这样一来,凡是能到达i的点,都能够到达j能到达的点。最后,任意一个能到达原B类点的点(其实就是所有点),都能够到达任意一点。
如果不存在无法达到i的A类点,那么说明任意一个点都能到达i,这种情况下,i向任意一个A类点连接都没问题。

现在,再来考虑考虑n≠m的问题
其实很简单,如果A类点多了,就先将多的那些A类点依次连接,最后形成一个n=m的图。
同理,如果B类点多了,也是将多余的B依次连接即可。
转移成n=m的图,需要消耗|n-m|条边,最后再消耗min(n,m)条边即可。

综上,答案就是max(n,m)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<stack>
#include<vector>
#define SF scanf
#define PF printf
#define MAXN 110
using namespace std;
int dfn[MAXN],low[MAXN],cnt,tot,sum,nx[MAXN];
vector<int> a[MAXN];
stack<int> s;
pair<int,int> l[MAXN*MAXN];
int in[MAXN],out[MAXN];
void dfs(int x){
    dfn[x]=++cnt;
    low[x]=dfn[x];
    s.push(x);
    for(int i=0;i<a[x].size();i++){
        if(dfn[a[x][i]]==0){
            dfs(a[x][i]);
            low[x]=min(low[x],low[a[x][i]]);
        }
        else
            if(nx[a[x][i]]==0)
                low[x]=min(low[x],dfn[a[x][i]]);
    }
    if(low[x]==dfn[x]){
        sum++;
        while(s.top()!=x){
            nx[s.top()]=sum;
            s.pop();
        }
        nx[s.top()]=sum;
        s.pop();
    }
}
int n;
int main(){
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);
    SF("%d",&n);
    int x,y;
    for(int i=1;i<=n;i++){
        SF("%d",&x);
        while(x){
            a[i].push_back(x);
            l[++tot]=make_pair(i,x);
            SF("%d",&x);
        }
    }
    for(int i=1;i<=n;i++)
        if(dfn[i]==0)
            dfs(i);
    /*PF("(%d)\n",sum);
    for(int i=1;i<=n;i++)
        PF("[%d:%d]\n",i,nx[i]);*/
    for(int i=1;i<=n;i++)
        a[i].clear();
    for(int i=1;i<=tot;i++){
        x=nx[l[i].first];
        y=nx[l[i].second];
        //PF("(%d %d)\n",x,y);
        if(x!=y)
            a[x].push_back(y);
    }
    for(int i=1;i<=sum;i++){
        out[i]=a[i].size();
        for(int j=0;j<a[i].size();j++)
            in[a[i][j]]++;
    }
    int ans1=0,ans2=0;
    if(sum==1){
        PF("1\n0");
        return 0;
    }
    for(int i=1;i<=sum;i++){
        if(in[i]==0)
            ans1++;
        if(out[i]==0)
            ans2++;
    }
    PF("%d\n%d",ans1,max(ans1,ans2));
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值