题目大意:
学校编号1到n,每个学校有一个列表,这个列表内包含其他学校。当一个学校拿到软件时会把软件分发给列表中的学校。求:
1.一个软件出现初始化问题至少需要给多少个学校才能覆盖所有学校。
2.需要在列表中添加多少学校才能使得分发给任一学校都可以覆盖所有学校。
解题思路:
在此贴一下kuangbin大佬的解析,真的跪了,太详细了。。。转载:kuangbin
给定一个有向图,求:
1) 至少要选几个顶点,才能做到从这些顶点出发,可以到达全部顶点
2) 至少要加多少条边,才能使得从任何一个顶点出发,都能到达全部顶点
顶点数<= 100
解题思路:
1. 求出所有强连通分量
2. 每个强连通分量缩成一点,则形成一个有向无环图DAG。
3. DAG上面有多少个入度为0的顶点,问题1的答案就是多少
在DAG上要加几条边,才能使得DAG变成强连通的,问题2的答案就是多少
加边的方法:
要为每个入度为0的点添加入边,为每个出度为0的点添加出边
假定有 n 个入度为0的点,m个出度为0的点,如何加边?
把所有入度为0的点编号 0,1,2,3,4 ....N -1
每次为一个编号为i的入度0点可达的出度0点,添加一条出边,连到编号为(i+1)%N 的那个出度0点,
这需要加n条边
若 m <= n,则
加了这n条边后,已经没有入度0点,则问题解决,一共加了n条边
若 m > n,则还有m-n个入度0点,则从这些点以外任取一点,和这些点都连上边,即可,这还需加m-n条边。
所以,max(m,n)就是第二个问题的解
此外:当只有一个强连通分支的时候,就是缩点后只有一个点,虽然入度出度为0的都有一个,但是实际上不需要增加清单的项了,所以答案是1,0;
这里第二个问题刚开始我是简单的以为把所有入度为0和出度为0的点的个数相乘即可。结果发现自己是在太傻了,存在更优的解法去完成这个要求。。。其实这个方法也并不复杂。
Ac代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<map>
#include<queue>
#include<stack>
#include<set>
#include<cmath>
#include<vector>
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
typedef long long ll;
const int maxn=1e4+5;
const int INF=1e9+7;
const ll mod=998244353;
int n,m,tot,cnt,dfn[maxn],low[maxn],belong[maxn],size[maxn],sl[maxn],sr[maxn];
vector<int> v[maxn],rv[maxn];
bool vis[maxn];
stack<int> s;
void tarjan(int u)
{
dfn[u]=low[u]=++tot;
vis[u]=1,s.push(u);
for(int i=0;i<v[u].size();i++)
{
int g=v[u][i];
if(!dfn[g])
{
tarjan(g);
low[u]=min(low[u],low[g]);
}
else if(vis[g]) low[u]=min(low[u],dfn[g]);
}
if(dfn[u]==low[u])
{
cnt++;
while(s.top()!=u)
{
size[cnt]++;
belong[s.top()]=cnt;
vis[s.top()]=0,s.pop();
}
size[cnt]++;
belong[s.top()]=cnt;
vis[s.top()]=0,s.pop();
}
}
void rebuild() //缩点重建
{
for(int i=1;i<=n;i++)
{
for(int j=0;j<v[i].size();j++)
{
int u=v[i][j];
if(belong[i]==belong[u]) continue;
rv[belong[i]].push_back(belong[u]);
}
}
for(int i=1;i<=cnt;i++) //去掉重复边 防止对度数计算造成影响
sort(rv[i].begin(),rv[i].end()),rv[i].erase(unique(rv[i].begin(),rv[i].end()),rv[i].end());
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
while(x)
{
v[i].push_back(x);
scanf("%d",&x);
}
}
for(int i=1;i<=n;i++)
if(!dfn[i]) tarjan(i);
rebuild();
for(int i=1;i<=cnt;i++)
{
sl[i]=rv[i].size(); //出度
for(int j=0;j<rv[i].size();j++)
{
sr[rv[i][j]]++; //入度
}
}
int ans1=0,ans2=0;
for(int i=1;i<=cnt;i++)
{
if(sl[i]==0) ans1++; //计算入度为0和出度为0的点的个数
if(sr[i]==0) ans2++;
}
if(cnt==1) printf("1\n0\n"); //特殊注意一个联通块的情况
else
{
printf("%d\n",ans2);
printf("%d\n",max(ans1,ans2));
}
//system("pause");
}