洛谷P2746 [USACO5.3]校园网Network of Schools--强连通分量缩点判断出度入度

题目描述

一些学校连入一个电脑网络。那些学校已订立了协议:每个学校都会给其它的一些学校分发软件(称作“接受学校”)。注意即使 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

题意:
分两问:
第一问:至少给几个学校输入信息,然后信息随着学校的传递关系可以到达所有学校,比如A->B,B->C,那么就可以A->C
第二问:至少将几个学校加入系统,使得给随意一个学校传入信息,所有学校都可以得到信息。

题解:第一问,非常明显直接强连通分量缩点,一个强连通分量里的所有学校,给任意一个学校传递学校其他学校都可以得到信息,那么我们就通过强连通分量缩点,把点之间建图,可以得到一棵树,那么树上节点入度为0的节点就是必须输入信息的节点,那么第一问的答案就是累计树上入度为0的节点个数,追踪到第二问,就是把树上一些点连接,让这个树变成一个新的图,而且这个图就是强连通图,那么就是累计树上入度为0节点数和出度为0的节点数,比较两者最大值,就是第二问答案,必须注意如果一开始整个图就是一个很强连通图,两问的答案都是1,需要特判。

#include<bits/stdc++.h>
using namespace std;
const int N=2e6;
typedef long long ll;
struct Node
{
	int v,next;
	ll w;
}edge[2*N];
//adge为初始的旧点的连接关系,edge为新节点的连接关系 
int head[N],cnt;
void add_edge(int u,int v,ll w)
{
	edge[cnt].v=v;
	edge[cnt].w=w;
	edge[cnt].next=head[u];
	head[u]=cnt++;
}
bool vis[N];
//sign[i]表示i节点属于第几个最大强连通图 
int DFN[N],LOW[N],sign[N];
stack<int>stk;
int ind,sign_ans,n,m;
void TarJan(int u)
{
	++ind;
	DFN[u]=LOW[u]=ind;
	stk.push(u);
	vis[u]=true;
	for(int i=head[u];i!=-1;i=edge[i].next)
	{
		int v=edge[i].v;
		if(!DFN[v])
		{
			TarJan(v);
			LOW[u]=min(LOW[u],LOW[v]);
		}
		else if(vis[v])
		LOW[u]=min(LOW[u],DFN[v]);
	}
	if(DFN[u]==LOW[u])
	{
		sign_ans++;
		int s;
//		cout<<"第"<<sign_ans<<"个最大强连通图: ";
		do{
			s=stk.top();
			stk.pop();
			//标记旧点属于哪个新点集合 
			sign[s]=sign_ans;
			vis[s]=false;
			//打印路径
//			cout<<s<<" "; 
		}
		while(u!=s);
//		cout<<endl;
	}
}
//入度inq,出度tnq 
int inq[N],tnq[N];
int main()
{
	scanf("%d",&n);
	cnt=0;
	memset(head,-1,sizeof(head));
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			int x;
			scanf("%d",&x);
			if(x==i)continue;
			if(x==0)break;
			add_edge(i,x,0);
		}
	} 
	sign_ans=0;
	for(int i=1;i<=n;i++)
	{
		if(!DFN[i])
		TarJan(i);
	}
	//开始缩点
	for(int i=1;i<=n;i++)
	{
		for(int j=head[i];j!=-1;j=edge[j].next)
		{
			int u=sign[i];
			int v=sign[edge[j].v];
			//标记树上的节点入度和出度
			if(u!=v)
			{
				tnq[u]++;
				inq[v]++;
			}
		}
	}
	int ans1=0,ans2=0;
	//开始累计
	for(int i=1;i<=sign_ans;i++)
	{
		if(inq[i]==0)
		ans1++;
		if(tnq[i]==0)
		ans2++;
	}
	//如果一开始就是一个强连通图
	if(sign_ans==1)
	cout<<1<<endl<<0<<endl;
	else cout<<ans1<<endl<<max(ans1,ans2)<<endl;
	return 0;
} 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值