(*)2018年全国多校算法寒假训练营练习比赛(第四场)E-通知小弟(Kosaraju)

链接:https://www.nowcoder.com/acm/contest/76/E
来源:牛客网

时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 32768K,其他语言65536K
64bit IO Format: %lld
题目描述 
        在战争时期,A国派出了许多间谍到其他国家去收集情报。因为间谍需要隐秘自己的身份,所以他们之间只是单向联系。所以,某个间谍只能单向联系到一部分的间谍。同时,间谍也不知道跟他联系的是谁。
HA是间谍们的老大,但他也只能联系到部分的间谍。HA现在有一项命令有告诉所有的间谍。HA想要知道他至少要告诉多少个他能联系上的间谍才能通知到所有的间谍。
输入描述:
有多个测试数据。
对于每个测试数据:
第一行为一个整数n,m(0<n,m<=500)代表间谍的数量和HA能通知到的间谍的数量(间谍的编号为1-n);
第二行为m个用空格隔开的整数xi,代表HA能通知到的间谍的编号;
第三行到第n+2行,每一行第一个整数ai(0<=ai<n)表示第i-2个间谍能单向联系到的间谍数。之后有ai个用空格隔开的整数,表示间谍i-2能单向联系到的间谍的编号。
输出描述:
输出一行,此行中有一个整数,代表HA至少需要联系的间谍数。如果HA不能通知到所有间谍,输出-1。
示例1
输入
3 2
1 2
1 2
1 1
0
输出
-1
示例2
输入
3 1
1
2 2 3
0
0
输出
1

题意:给一张有向图和给几个点。问能否通过这几个点遍历整张有向图,如果能,最少要访问几个点?

思路:一开始没有想太多,强行dfs两次(很暴力的方法),然后TLE了。后来发现这题做法还挺多,可以用并查集或者强连通子图做,强连通子图又有kosaraju算法tarjan算法,一开始是准备随大流tarjan的,无奈看了半天没看懂。为了快速把题AC,转向kosaraju,比较好理解,但是效率略低于tarjan。

求出强连通子图后,还用到了缩点的概念,又学习了一波。

简单说下kosaraju:1.先对原图dfs1遍历,并按图的后序压栈

                                2.按出栈的顺序对逆图dfs2遍历,每遍历完一次得到一个强连通子图,dfs2期间保存一下每个点属于哪个强连通子图。

                                3.对每一个强连通子图缩点简化(看成一个点),遍历每条边(如果头尾结点都属于同一个强连通子图就不增加indegree),求入度为0的点。

                                4.最后只要看老大是否能联系上每个入度为0的强连通子图即可。(离散没好好学,这个过程我还稍微想了一下,只要每个入度为0的点,都搜了,就全图都搜了)

关于tarjan和并查集的解法后期跟进。

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<stack>
#define IO ios::sync_with_stdio(false);cin.tie(0);
#define INF 0x3f3f3f3f
typedef long long ll;
using namespace std;
vector<int> t[1010], rt[1010];
stack<int> S;
int n, m, a[1010], k, vis[1010], cnt, belong[1010], indegree[1010];
void dfs1(int cur)//逆后序遍历 
{
	vis[cur] = 1;
	for(int i = 0; i < t[cur].size(); i++){
		if(!vis[t[cur][i]]){
			dfs1(t[cur][i]);
		}
	}
	S.push(cur);
} 
void dfs2(int cur)//对逆图搜索 
{
	vis[cur] = 1;
	//cout << cur;
	belong[cur] = cnt;//记录该点属于几号强连通子图 
	for(int i = 0; i < rt[cur].size(); i++){
		if(!vis[rt[cur][i]]){
			dfs2(rt[cur][i]);
		}
	}
}
void kosaraju()
{
	for(int i = 1; i <= n; i++){//1~n号点 
		if(!vis[i]){
			dfs1(i);
		}
	}
	memset(vis, 0, sizeof(vis));
	while(!S.empty()){
		if(!vis[S.top()]){
			dfs2(S.top());
			cnt++;
			//cout << endl;
		}
		S.pop();
	} 
}
int find(int c)//传递的是强联通子图号 
{
	for(int i = 1; i <= n; i++){
		if(belong[i] == c){ 
			if(a[i]){//能联系上一个 
				return 1;
			}
		}
	}
	return 0;
}
int main()
{
	IO;
	int xx, yy;
	while(cin >> n >> m){
		cnt=0;
		for(int i = 0; i <= n; i++){
			t[i].clear();rt[i].clear();
		}
		while(!S.empty()) S.pop();
		memset(a, 0, sizeof(a));
		memset(vis, 0, sizeof(vis));
		memset(indegree, 0, sizeof(indegree));
		memset(belong, 0, sizeof(belong));
		for(int i = 0; i < m; i++){
			cin >> xx;
			a[xx] = 1;
		}
		for(int i = 1; i <= n; i++){
			cin >> k;
			for(int j = 0; j < k; j++){
				cin >> xx;
				t[i].push_back(xx);
				rt[xx].push_back(i);//逆图 
			}
		}
		kosaraju();
		//缩点后,求入度为0的点 
		for(int i = 1; i <= n; i++){
			for(int j = 0; j < t[i].size(); j++){
				if(belong[i] != belong[t[i][j]]){//若两个点不在同一强连通子图 
					indegree[belong[t[i][j]]]++;//入度++ 
				}
			}
		}
		//老大能否联系上每个入度为0的强连通子图(每个图中有一个点能联系上即可) 
		int flag=1, ans=0;
		for(int i = 0; i < cnt; i++){
			if(!indegree[i]){
				if(find(i)){
					ans++;
				}
				else{
					flag=0;
					break;
				}
			}
		}
		if(!flag){
			cout << "-1" << endl;
		}
		else cout << ans << endl;
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值