Tarjan缩点 [USACO5.3]校园网Network of Schools (洛谷 P2746)

[USACO5.3]校园网Network of Schools

题目描述

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

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

输入格式

输入文件的第一行包括一个正整数 N,表示网络中的学校数目。学校用前 N 个正整数标识。

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

输出格式

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

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

第二行应该包括一个非负整数,表示子任务 B 的解。


这道题就是Tarjan 缩点的比较模板的题了,但是比较重要的一点是要明白DAG图要变成一个强连通图,要加的边数是 出度为0的点数和入度为0的点数 的最大值;

代码:

#include<bits/stdc++.h>
#define LL long long
#define pa pair<int,int>
#define ls k<<1
#define rs k<<1|1
#define inf 0x3f3f3f3f
using namespace std;
const int N=20010;
const int M=1000100;
const LL mod=100000000;
int dfn[N],low[N],tot,head[N],cnt,n,sta[N],top,fa[N],in[N],out[N],sum;
vector<int>R[N];
bool vis[N],ma[N][N];
struct Node{
	int to,nex;
}edge[M];
void add(int p,int q){
	edge[cnt].to=q;
	edge[cnt].nex=head[p];
	head[p]=cnt++;
}
void Tarjan(int p){
	dfn[p]=low[p]=++tot;
	if(!vis[p]) vis[p]=true,sta[++top]=p;
	for(int i=head[p];~i;i=edge[i].nex){
		int q=edge[i].to;
		if(!dfn[q]){
			Tarjan(q);
			low[p]=min(low[p],low[q]);
		}
		else if(vis[q]) low[p]=min(low[p],dfn[q]);
	}
	if(dfn[p]==low[p]){
		sum++;//总点数 
		fa[p]=p;
		vis[p]=false;
		while(sta[top]!=p){
			vis[sta[top]]=false;
			fa[sta[top]]=p;
			top--;
		}
		top--;
	}
}
int main(){
	memset(head,-1,sizeof(head));
	cin>>n;
	for(int i=1;i<=n;i++){
		int q;
		while(1){
			scanf("%d",&q);
			if(q==0) break;
			add(i,q);
			R[i].push_back(q);
		}
	}
	for(int i=1;i<=n;i++){
		if(!dfn[i]) Tarjan(i);
	}
	for(int i=1;i<=n;i++){
		for(int j=0;j<R[i].size();j++){
			if(fa[i]!=fa[R[i][j]]&&!ma[fa[i]][fa[R[i][j]]]){
				ma[fa[i]][fa[R[i][j]]]=true;
				in[fa[R[i][j]]]++;
				out[fa[i]]++;
			}
		}
	}
	int ans1=0,ans2=0;
	for(int i=1;i<=n;i++){
		if(fa[i]==i&&in[i]==0) ans1++;
		if(fa[i]==i&&out[i]==0) ans2++;
	}
	if(sum==1) cout<<1<<endl<<0<<endl;
	else cout<<ans1<<endl<<max(ans1,ans2)<<endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值