题意:给定你n个点,然后这些点之间会形成一个有向图,然后有两个任务要求。
1:问你最少选择几个节点,使得从这些点出发能发,能够到达所有的顶点。2:问你最少在这个图中加几条边,使得从添加后的图中任意一点出发,能够达到其余的所有顶点。
思路:我们要先用tarjan进行一遍原图的缩点,然后在缩完点后的新图里,对于任务1,我们可以知道,只要找到这个图中的所有入度为0的强联通分量的数目即可。而对于任务二,我们可以知道需要把入度为0的强联通分量和出度为0的强联通分量的最大值。
我们可以加了这些红色的边之后,这个图就可以满足我们任务二的需求了,所以两个任务就变成了,求解强连通分量中,入度为0和出度为0
的即可。
代码:
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <set>
//tarjan算法 求强联通分量
using namespace std;
typedef long long ll;
const int MAXN = 1e4 + 7;
int head[MAXN],hd[MAXN],cnt,tot,indegree[MAXN],outdegree[MAXN];
struct Edge{
int next,to;
}edge[MAXN<<1],e[MAXN<<1];
void addedge(int u,int v){ edge[++cnt].to = v;;edge[cnt].next = head[u];head[u] = cnt; }
int dfn[MAXN],low[MAXN];//dfn数组就是时间戳 low数组代表的是当前的点及其后续的点能连接到的深度最浅的点的时间戳为多少
int sta[MAXN],scc[MAXN],time,sccnum,top;//sta维护一个模拟栈来求解scc scc代表当前前点属于哪个强联通分量
//时间复杂度 O(N + V)
void tarjan(int u){
if(dfn[u]) return;
dfn[u] = low[u] = ++time;//一开始low数组先指向自己
sta[++top] = u;
for(int i = head[u];i;i = edge[i].next){
int v = edge[i].to;
if(!dfn[v]){//如果v节点未被访问过
tarjan(v);
low[u] = min(low[u],low[v]);
}
else if(!scc[v]){//如果还在栈内
low[u] = min(low[u],low[v]);
}
}
if(low[u] == dfn[u]){//后代不能找到更浅的点 就以当前点和栈内节点形成了一个强连通分量
sccnum++;
while(1){
int x = sta[top--];
scc[x] = sccnum;
if(x == u) break;//强联通分量寻找完成
}
}
}
int main()
{
int n;
scanf("%d",&n);
int x;
for(int i = 1;i <= n;i ++){
while(~scanf("%d",&x)){
if(x == 0) break;
addedge(i,x);
}
}
for(int i = 1;i <= n;i ++){//每一个点 都要tarjan 缩一下点
if(!dfn[i]) tarjan(i);
}
//cout<<"---"<<sccnum<<endl;
if(sccnum == 1){
printf("1\n0\n");
return 0;
}
//求缩完点后的新图 属于一个强联通分量的点都缩成了一点
for(int u = 1;u <= n;u ++){
for(int i = head[u];i;i = edge[i].next){
int x = u,y = edge[i].to;
if(scc[x] != scc[y])
indegree[scc[y]]++,outdegree[scc[x]]++;
}
}
int ans1 = 0,ans2 = 0,t1 = 0,t2 = 0;
for(int i = 1;i <= sccnum;i ++){
if(indegree[i] == 0)
ans1++,t1++;
if(outdegree[i] == 0)
t2++;
}
ans2 = max(t1,t2);
printf("%d\n%d\n",ans1,ans2);
return 0;
}