IOI1996 校园网

传送门

题目描述

一些学校连入一个电脑网络。那些学校已订立了协议:每个学校都会给其它的一些学校分发软件(称作“接受学校”)。注意即使 B 在 A 学校的分发列表中, A 也不一定在 B 学校的列表中。

你要写一个程序计算,根据协议,为了让网络中所有的学校都用上新软件,必须接受新软件副本的最少学校数目(子任务 A)。更进一步,我们想要确定通过给任意一个学校发送新软件,这个软件就会分发到网络中的所有学校。为了完成这个任务,我们可能必须扩展接收学校列表,使其加入新成员。计算最少需要增加几个扩展,使得不论我们给哪个学校发送新软件,它都会到达其余所有的学校(子任务 B)。一个扩展就是在一个学校的接收学校列表中引入一个新成员。

输入输出格式

输入格式:

输入文件的第一行包括一个整数 N:网络中的学校数目(2 <= N <= 100)。学校用前 N 个正整数标识。

接下来 N 行中每行都表示一个接收学校列表(分发列表)。第 i+1 行包括学校 i 的接收学校的标识符。每个列表用 0 结束。空列表只用一个 0 表示。

输出格式:

你的程序应该在输出文件中输出两行。

第一行应该包括一个正整数:子任务 A 的解。

第二行应该包括子任务 B 的解。

输入输出样例

输入样例#1:

5
2 4 3 0
4 5 0
0
0
1 0

输出样例#1:

1
2

说明

题目翻译来自NOCOW。

USACO Training Section 5.3

Solution

很明显的tarjan缩点。最后入度为0的缩点个数即为A的答案。

至于B的答案有点妙,看了很多题解都没有讲清楚,最后找学长才解决掉的。

首先,我们要知道我们要做什么:把一个DAG变成强连通分量的最少连边数。

然后就很容易知道,强连通分量里肯定是不存在入度或者出度为0的点。

那我们为了消掉一个入度或者出度为0的点,是不是要至少连一条边。

然后我们会发现,如果你从一个出度为0的点,连一条边到一个入度为0的点,刚刚好就可以消掉一对点。

这样一一对应,最后可能会剩下一些点没有连,只需要随便连即可。

这样B的答案就是max(入度为0的点数,出度为0的点数)。

具体要怎么连边?

在完成缩点后,可能会有很多联通块,每个联通块都有自己的入度为0的点和出度为0的点,我们只需要把一个出度为0的点,连到另一个联通块的某个入度为0的点,通过这个点可以到达其他出度为0的点,然后到达其他入度为0的点………

#include <cstdio>
#include <cstring>
#include <algorithm>
#define MAXN 105

int head[MAXN];
struct edge{
    int v,next;
}G[MAXN*MAXN];

int vis[MAXN],st[MAXN],top = 0;
int color[MAXN],low[MAXN],dfn[MAXN];
int _in[MAXN],_out[MAXN];

int N,tot = 0,color_num = 0;

inline void add(int u,int v){
    G[++tot].v = v;G[tot].next = head[u];head[u] = tot;
}

int num = 0;
void tarjan(int u){
    dfn[u] = low[u] = ++num;
    vis[u] = 1;st[++top] = u;
    for(register int i=head[u];i;i=G[i].next){
        int v = G[i].v;
        if(!dfn[v]){
            tarjan(v);
            low[u] = std::min(low[u],low[v]);
        }
        else if(vis[v]){
            low[u] = std::min(low[u],dfn[v]);
        }
    }

    if(low[u]==dfn[u]){
        ++color_num;color[u] = color_num;
        vis[u] = 0;
        while(st[top]!=u){
            color[st[top]] = color_num;
            vis[st[top--]] = 0;
        }
        top--;
    }
}
int main(){

    std::memset(vis,0,sizeof(vis));
    std::memset(low,0,sizeof(low));
    std::memset(dfn,0,sizeof(dfn));
    std::memset(_in,0,sizeof(_in));
    std::memset(_out,0,sizeof(_out));

    scanf("%d",&N);
    int v;
    for(register int i=1;i<=N;++i){
        while(scanf("%d",&v)&&v!=0){
            add(i,v);
        }
    }

    for(register int i=1;i<=N;++i){
        if(!dfn[i])tarjan(i);
    }

    int count_in = 0;
    int count_out = 0;

    for(register int i=1;i<=N;++i){
        for(register int j=head[i];j;j=G[j].next){
            if(color[i]==color[G[j].v])continue;
            _in[color[G[j].v]]++;
            _out[color[i]]++;
        }
    }

    for(register int i=1;i<=color_num;++i){
        if(_in[i]==0)count_in++;
        if(_out[i]==0)count_out++;
    }

    printf("%d\n",count_in);
    printf("%d",color_num==1?0:std::max(count_in,count_out));
    return 0;
}

转载于:https://www.cnblogs.com/Neworld2002/p/9539405.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值