洛谷 P2746 [USACO5.3]校园网

题面

学校间构成一个有向图,当这个学校得到软件时,它会拷贝给所有的目标
任务A:为了让所有学校都可用上软件,最少需要给几个学校投放软件
任务B:为了让给任意一个学校投放软件,其他学校都可以得到软件,需要新增多少条有向边
学校数<=100

分析

首先用强连通分量缩点到DAG情况
特殊情况:整个图是一个强连通分量,那么任务A=1,任务B=0

普遍情况
考虑任务A,对于任何一个入度0的点(包括缩点形成的点),都必须得有投放安排(否则不可能从别的地方将软件给他拷贝),这就是任务A的结果

任务B:
相当于把DAG通过加边再变成强连通分量,策略是通过连接出度0的点和入度0的点,使得所有出度0点和入度0的点消失,连接并非随意,而是有策略(在这个题没有体现,只求数量),这里只是说明存在这一情况,并非只是简单消灭出入度0的点即可。

蓝色虚线是新增边,黑色线是原本存在的。
不难发现,消灭入度0需要A答案那么多边,消灭出度0需要出度0的点的数量那么多边。
综合一下,取max即可

代码

#include "cstdlib"
#include <iostream>
#include<algorithm>
#include<vector>
#include<stack>
using namespace std;
vector<int> G[10004];
int dfs_clock = 0, scc_cnt = 0;//时间戳,强连通分量组编号
int sccno[104];//表示每个点属于哪个强连通块
int pre[104];//dfs序
int low[104];//low[u]表示u及其后带通过反向边最多能返回到哪
stack<int> s;//存当前的节点
int in[104];//每一组强连通分量的入度统计
int out[104];//每一组出度统计
void dfs(int u)
{
	pre[u] = low[u] = ++dfs_clock;
	s.push(u);
	for (int i = 0; i < G[u].size(); i++)
	{
		int v = G[u][i];//下一个
		if (!pre[v])//未到过v
		{
			dfs(v);
			low[u] = min(low[u], low[v]);
		}
		else if (!sccno[v])//有可能是新的反向边
		{
			low[u] = min(low[u], pre[v]);
		}
	}
	if (low[u] == pre[u])//是第一次发现这个连通分量的点
	{
		scc_cnt++;
		while (true)
		{
			int x = s.top();s.pop();
			sccno[x] = scc_cnt;
			if (x == u)break;
		}
	}
}
void find_scc(int n)
{
	for (int i = 1; i <= n; i++)
	{
		if (!pre[i])dfs(i);
	}
}
int main()
{
	ios::sync_with_stdio(false);
	int n, u, v;
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		while (true)
		{
			cin >> v;
			if (v == 0)break;
			G[i].push_back(v);
		}
	}
	find_scc(n);//开始寻找强连通分量的函数

	//sccno已经处理完,1~scc_cnt

	//开始处理每个块的入度问题(A任务),0入度必须安排
	for (int i = 1; i <= n; i++)
	{
		for (int j = 0;j < G[i].size();j++)//(i,G[i][j])是一条边
		{
			if (sccno[i] != sccno[G[i][j]])in[sccno[G[i][j]]]++,out[sccno[i]]++;//不是同一个块,则G[i][j]所属连通块的入度++
		}
	}
	int totin = 0,totout=0;//此时记录in为0的块的个数
	for (int i = 1; i <= scc_cnt; i++)
	{
		if (in[i] == 0)totin++;
		if (out[i] == 0)totout++;
	}
	cout << totin<<endl;
	if (scc_cnt > 1)cout << max(totin, totout);
	else cout << 0;

	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值