题面
学校间构成一个有向图,当这个学校得到软件时,它会拷贝给所有的目标
任务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;
}