【训练题】强连通分量缩点 P1679
Description
有 N 个人和每个人所认识人的列表,注意:即使B在A的列表中,A也不一定在B的列表中。现在小明有一个重要消息要通知这N个人,注意:如果A认识B,则当A得到这个消息,他就会立即通知B。
现在请你完成下面两个任务:
任务1:请你计算要让N个人都得到消息,那么小明必须把这个消息直接通知的人的最少数目。
任务2:如果小明想要只告诉这N个人中的任何一个人,其他所有人都能得到消息,那么可能需要在某些人的认识列表添加认识的新成员。请你计算,最少添加多少新成员,就可以让任何一个人得到消息,都能传到其他所有人。
Input
第一行包括一个整数 N:表示人数,这些人编号依次为1..N。接下来 N 行中每行都表示一个认识关系的列表,第 i+1 行包括表示第 i 人认识的人的列表,每个列表用 0 结束,空列表只用一个 0 表示。
Output
第一行包括一个正整数:任务 1 的解。第二行应该包括任务2 的解。
Hint
N<=10000
Solution
首先这道题要用tarjan缩点成一个DAG图,之后统计入度和出度为1的点,这些点就需要一条边来形成环。边的数量取入度和出度为1的点的数量的最大值就可以了因为加1条边可以分别处理一个入度为1和出度为1的点,单独的点就需要单独一条边来处理。tarjan找强连通分量的原理是把点压进栈里进行dfs,对dfn值为0的点进行tarjan更新dfn这个时间戳,把low设置为u和v里面low值最小的,然后当v还没有属于任何一个强连通分量的时候,把s的low值更新成为low[u]和dfn[v]里的最小值。如果他们属于同一个连通分量他们的low值都是祖先结点的low值。这个过程类似于动态规划。当找到一条返祖边,也就是low[s]dfn[s]的时候,就把在s之前的点全部弹出并记录在一个强连通分量里直到把s弹出来。
由于图不一定连通而一个孤点也是一个强连通分量所以只要!dfn[i]就要进行tarjan。最后枚举所有边,要是有不在同一个强连通分量的两个点之间有边,起点这个强连通分量的出度就++,终点的强连通分量的入度就++。然后枚举缩点后的所有强连通分量,用ans1和ans2记录入度为0和出度为0的点的数量,就完了。
注意事项:
1、如果所有的点都在一个强连通分量里面,就不需要加边,所以if(cnt1)ans=0这个语句是必要的。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<stack>
#include<algorithm>
#define maxn 1000010
using namespace std;
struct Edge{
int u;
int v;
int next;
}edge[maxn];
stack<int>stk;
int first[maxn],last[maxn],low[maxn],dfn[maxn],belong[maxn],rd[maxn],cd[maxn];
int node,n,x,hhhh,cnt,ans1,ans2,ans;
void addedge(int u,int v){
edge[++node]=(Edge){u,v,0};
if(first[u]==0)first[u]=node;
else edge[last[u]].next=node;
last[u]=node;
return;
}
void init(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
while(1){
scanf("%d",&x);
if(x==0)break;
addedge(i,x);
}
}
return;
}
void tarjan(int s){
low[s]=dfn[s]=++hhhh;
stk.push(s);
for(int q=first[s];q;q=edge[q].next){
int p=edge[q].v;
if(dfn[p]==0){
tarjan(p);
low[s]=min(low[s],low[p]);
}
else if(!belong[p]){
low[s]=min(low[s],dfn[p]);
}
}
if(low[s]==dfn[s]){
cnt++;
belong[s]=cnt;
while(stk.top()!=s){
belong[stk.top()]=cnt;
stk.pop();
}
stk.pop();
}
}
void solve(){
for(int i=1;i<=n;i++){
if(!dfn[i]){
tarjan(i);
}
}
for(int i=1;i<=n;i++){
for(int q=first[i];q;q=edge[q].next){
int p=edge[q].v;
if(belong[i]!=belong[p]){
rd[belong[p]]++;
cd[belong[i]]++;
}
}
}
for(int i=1;i<=cnt;i++){
if(rd[i]==0)ans1++;
if(cd[i]==0)ans2++;
}
}
int main(){
init();
solve();
printf("%d\n",ans1);
ans=max(ans1,ans2);
if(cnt==1)ans=0;
printf("%d\n",ans);
return 0;
}