【训练题】强连通分量缩点 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(cnt
1)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;
}
posted @ 2018-11-30 16:44  虚拟北方virtual_north。  阅读(287)  评论(0编辑  收藏  举报