题意:给出一个有向图
输出1:至少要向多少台电脑放文件能使所有电脑都能得到文件
输出2:至少加多少条边,可以使得在任意地方放文件,文件都能到达任意一台电脑
思路:从题意上来看就是 寻找有向图中的强连通分量(极大强连通子图)
输出2即是:在DAG上要加几条边,才能使得DAG变成强连通图
所以使用tarjan算法,我们就把强连通分量缩成一个点,然后再根据这些点的出入度来增加边
假设n个入度为0的点,m个出度为0的点
(1)若m<=n 加了n边后 连接入度和出度为0的点
(2)若m >n,则还有m-n个入度为0的点没有解决,所以还要加上m-n边
即加边为max(m,n); 如果只有一个强连通分量时就不需要加边了
完整代码:(详细注解)
#include <cstdio> #include <stack> #include <iostream> #include <cstring> #include <algorithm> using namespace std; const int maxn = 1e3; const int maxm = 1e5; struct Edge{ int v,next; int u; }edge[maxm]; int head[maxn]; int top,num; int dfn[maxn],low[maxn],belong[maxn];//belong相当于染色,把同一个连通图标记为一种颜色 int in[maxn],out[maxn];//出入度 int instack[maxn];//标记是否在栈中 int index; stack<int>Stack; void tarjan(int x){ //(1)结点u初值 low[x] = dfn[x] = ++index; instack[x] = 1; //(2)压入栈中 Stack.push(x); //(3)枚举每一条边 for(int i = head[x]; ~i;i= edge[i].next){ //(4)入过结点u为强连通分量的根节点时 int v = edge[i].v; //(5)没有被访问时候递归搜索,搜到底了之后就并到父节点 if(!dfn[v]){ tarjan(v); low[x] = min(low[x],low[v]); } //(5*)如果结点已在栈中就并其时间戳:因为在栈中意味着先就被访问过,也就是该结点的祖先结点 else if(instack[v]){
low[x] = min(low[x],low[v]); } } //(6)找到根部分 if(low[x] == dfn[x]){ int t; ++num;//强连通分量个数 do{ t = Stack.top(); belong[t] = num; //出栈 instack[t] = 0; Stack.pop(); }while(t != x);//直到退出结点位置到其本身 } } void add(int u,int v){ edge[top].v = v; edge[top].next = head[u]; head[u] = top++; } void init(){ memset(head,-1,sizeof(head)); memset(instack,0,sizeof(instack)); memset(in,0,sizeof(in)); memset(out,0,sizeof(out)); // memset(dfn,0,sizeof(dfn)); // memset(low,0,sizeof(low)); // memset(belong,0,sizeof(belong)); } int main(){ init(); int n,x; cin>>n; for(int i = 1;i<=n;i++){ while(cin>>x&&x) add(i,x); } for(int i = 1;i<=n;i++){ //没有被访问就进行tarjan if(!dfn[i]) tarjan(i); } if(num == 1) { cout<<1<<endl<<0<<endl; return 0; } for(int i = 1;i<=n ; i++){ for(int j = head[i]; ~j ; j = edge[j].next){ int v = edge[j].v; //如果不在一个分量中(即缩点之后的结点关系) if(belong[i] != belong[v]){ out[belong[i]]++; in[belong[v]]++; } } } int sum1,sum2; sum1 = sum2 = 0; for(int i = 1; i <= num;i++){ if(!out[i]) sum1++; if(!in[i]) sum2++; } cout<<sum2<<endl<<max(sum1,sum2)<<endl; return 0; }