2020-12-06

牛客 通知小弟

https://ac.nowcoder.com/acm/problem/15120

题目大意
给一个n个点的有向图,你可以从中选择一些点,然后从这些点开始沿着边能遍历所有点。求至少需要选择多少点才可以达到该目的。

网上有各种解法,我看到过最厉害的博主可以给出4种思路,非常的强。但是蒟蒻就会了一种强连通分量的求法,用时23ms速度还可以。

下面献上思路。

假设一个点A可以被HA以外的点B通知,那么我们只需要通知那个点B就可以通知到点A了,以此类推一直向上走,会出现两种情况
1.这个点无法被其他人通知,只能被HA通知,那么这个点肯定是HA必须要通知的点。
2.这个点和之前找到的点构成了一个强连通分量,也就是一个环,环内的人都可以相互通知。

如果我们使用tarjan求强连通分量,然后根据强连通分量之后的结果可以发现,这两类点的入度都是为0的,这些入度为0的点就是HA需要通知的点。

我们可以细细推敲一下,分成三种情况。
1.点没有构成环

那么整个就是一条带分叉的直链,只需要通知最开始的原点(入度为0)就可以直接干掉整个直链

2.所有点构成了环

那么随意通知一个点就可以干掉整个环,由于环是强连通分量,在缩点之后会变成一个点,自然入度就为0,答案加一即可。

3.两种情况的组合,这里请各为读者自行画图进行缩点模拟,然后看结果

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,sum,tot,cnt;
struct node {
	int v,next;
}; 
struct node edge[200005];
int vis[602],low[602],dfn[602],scc[602],head[602],start[602],du[602],ins[602];
stack<int>q;
void add(int u,int v){
	edge[tot].v=v;
	edge[tot].next=head[u];
	head[u]=tot;
	tot++;
}
void init(){
	for(int i=1;i<=n;i++){
		vis[i]=0;
		dfn[i]=-1;
		head[i]=-1;
	}
}
void tarjan(int now){
	sum++;
	dfn[now]=low[now]=sum;
	q.push(now);
	ins[now]=1;
	for(int i=head[now];i!=-1;i=edge[i].next){
		int v=edge[i].v;
		if(dfn[v]==-1){
			tarjan(v);
			low[now]=min(low[now],low[v]);
		}
		else if(ins[v])low[now]=min(low[now],dfn[v]);
	}
	if(dfn[now]==low[now]){
		cnt++;
		int v=-1;
		do{
			v=q.top();q.pop();
			ins[v]=0;scc[v]=cnt;
		}while(v!=now);
	}
}
void dfs(int now){
	vis[now]=1;
	for(int i=head[now];i!=-1;i=edge[i].next){
		int v=edge[i].v;
		if(vis[v]==0)dfs(v);
	} 
}
int main (){
	#ifdef LOCAL
	freopen(".\\a.in", "r", stdin);
	#endif
	scanf("%d%d",&n,&m);
	init();
	for(int i=1;i<=m;i++){
		scanf("%d",&start[i]);
	}
	for(int i=1;i<=n;i++){
		int tmp;
		scanf("%d",&tmp);
		for(int j=1;j<=tmp;j++){
			int v;
			scanf("%d",&v);
			add(i,v);
		}
	}
	for(int i=1;i<=m;i++){//直接暴力搜索一次,用vis数组标记
		if(vis[start[i]]==0){
			dfs(start[i]);
		}
	}
	for(int i=1;i<=n;i++){
		if(vis[i]==0){//有不能到达的节点直接-1
			printf("-1\n");
			return 0;
		}
	}
	for(int i=1;i<=n;i++){//对所有的点进行tarjan求强连通分量
		if(dfn[i]==-1)tarjan(i);
	}
	for(int i=1;i<=n;i++){//对缩点后的图的入度进行计算
		for(int j=head[i];j!=-1;j=edge[j].next){
			int u=scc[i];
			int v=scc[edge[j].v];//取缩点之后的节点
			if(u!=v)du[v]++;//如果节点不同,说明不处于同一个强连通分量,两点之间的边有效
		}
	}
	int ans=0;
	for(int i=1;i<=cnt;i++){
		if(du[i]==0){//最后直接统计就可以了
			ans++;
		}
	}
	printf("%d\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值