题目链接:http://poj.org/problem?id=1236
题意:有n个学校,学校之间的网络由单向边连接,现在有一个软件要传向每个学校,(单向连通的可以直接传达)
问最少要传给多少个学校可以全部传达?
再至少添加几条单向边,可以随便发送给一个学校使全部学校传达到。
思路:
先找到连通分量,每个连通分量入度为0的就是要传达的,第一问就是入度为0的个数
第二问就是添加几条边可以得到使整个图变成一个强连通分量,答案就是连通分量入度为0的个数和出度为0的个数中最大的那个值
原因:我们将缩完点之后的图看成一个个子树,要想把它们变成一个连通分量就要将出度为0和入度为0的点连起来。
所以只要只要找两者最大的就行。
为什么缩点后再计算出度入度呢?
因为不缩点的话,可能有环,以一个一个点为单位用出度是算不出答案来,只有以一个一个连通分量为单位,计算出度入度。
代码:
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> #define inf 0x3f3f3f3f using namespace std; typedef long long ll; const int maxn=105; struct node{ int to,next; }edge[maxn*maxn]; int head[maxn],low[maxn],dfn[maxn],st[maxn];//st模拟栈 int in[maxn],out[maxn],visit[maxn],belong[maxn];//belong每个点属于哪个连通分量 int n,num,cnt,tot,top,ans1,ans2; void init()//初始化 { memset(head,-1,sizeof(head)); memset(visit,0,sizeof(visit)); memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); memset(in,0,sizeof(in)); memset(out,0,sizeof(out)); memset(st,0,sizeof(st)); memset(belong,0,sizeof(belong)); ans1=ans2=cnt=tot=num=top=0; } void add(int u,int v)//前向星加边 { edge[cnt].to=v; edge[cnt].next=head[u]; head[u]=cnt++; } void tarjan(int u)//tarjan缩点 { dfn[u]=low[u]=++tot;//赋初值 visit[u]=1;//标记进栈 st[++top]=u;//进栈 for(int i=head[u];i!=-1;i=edge[i].next)//搜索相连的边 { int v=edge[i].to; if(!dfn[v])//没有被搜索过 { tarjan(v);//搜索 low[u]=min(low[u],low[v]); } else if(visit[v])//搜索过,已经在栈里 low[u]=min(low[u],dfn[v]); } if(dfn[u]==low[u])//一个连通分量 { int t; num++;//联通分量的编号 do{ t=st[top--];//出栈 visit[t]=0;//取消标记 belong[t]=num;//记录所在连通分量 }while(t!=u); } } void solve() { for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i); for(int i=1;i<=n;i++) { for(int j=head[i];j!=-1;j=edge[j].next) { int v=edge[j].to; if(belong[i]!=belong[v])//不是同一个缩点 { out[belong[i]]++; in[belong[v]]++; } } } for(int i=1;i<=num;i++) { if(!in[i]) ans1++; if(!out[i]) ans2++; } } int main() { while(scanf("%d",&n)!=EOF) { init(); int x; for(int i=1;i<=n;i++) { while(scanf("%d",&x) && x!=0) add(i,x); } solve(); ans2=max(ans1,ans2); if(num==1) printf("1 0 "); else printf("%d %d ",ans1,ans2); } return 0; }