367. 学校网络(有向图的强连通分量,tarjan算法)

367. 学校网络 - AcWing题库

一些学校连接在一个计算机网络上,学校之间存在软件支援协议,每个学校都有它应支援的学校名单(学校 A 支援学校 B,并不表示学校 B 一定要支援学校 A)。

当某校获得一个新软件时,无论是直接获得还是通过网络获得,该校都应立即将这个软件通过网络传送给它应支援的学校。

因此,一个新软件若想让所有学校都能使用,只需将其提供给一些学校即可。

现在请问最少需要将一个新软件直接提供给多少个学校,才能使软件能够通过网络被传送到所有学校?

最少需要添加几条新的支援关系,使得将一个新软件提供给任何一个学校,其他所有学校就都可以通过网络获得该软件?

输入格式

第 1 行包含整数 N,表示学校数量。

第 2..N+1 行,每行包含一个或多个整数,第 i+1 行表示学校 i 应该支援的学校名单,每行最后都有一个 0 表示名单结束(只有一个 0 即表示该学校没有需要支援的学校)。

输出格式

输出两个问题的结果,每个结果占一行。

数据范围

2≤N≤100

输入样例:
5
2 4 3 0
4 5 0
0
0
1 0
输出样例:
1
2

解析: 


性质:我们发现强连通分量内任意一个点收到软件,强连通分量内的其他点都能收到软件,所经过tarjan算法缩点后,形成的一个无环的有向连通度,形成的一棵树,对于第一个问的答案,就是树的个数;第二个问的答案是max{树的个数,叶子节点的个数}


对于第二个答案的证明:我们设 q=树的根结点数,p=叶子节点的个数
不妨让:q<p;
若想让整个图的所有结点相互可达,这个图必须没有出度为0的点,所以所有的叶子节点最少都必须引出一个出边,即,整个图至少要增加p条边;
我们可以让叶子节点的出边都指向自己的起点(根节点),但我们发现,如果从搜索树的任意一个叶子节点引一条边到另一个搜索树的根节点,另一棵也以同样方式因一条边回来,边数并没有增加,但他们构成了一个强连通分量;
我们可以将这个方式推广到多棵搜索树;最终,我们只增加了p边,就让整个图成了一个强连通图,同时,又因为我们至少要新增p条边才能保证整个图的所有结点相互可达(强连通),所以p即是最优解。
 

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<math.h>
#include<map>
using namespace std;
typedef long long LL;
const int N = 110, M = 1e4 + 5;
int n;
int h[N], e[M], ne[M], idx;
int dfn[N], low[N],timestamp;
int stk[N], top;
bool instk[N];
int din[N], dout[N];
int scc_cnt,id[N],siz[N];

void add(int a, int b) {
	e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

void tarjan(int u) {
	dfn[u] = low[u] = ++timestamp;
	stk[++top] = u;
	instk[u] = 1;
	for (int i = h[u]; i != -1; i = ne[i]) {
		int j = e[i];
		if (!dfn[j]) {
			tarjan(j);
			low[u] = min(low[j], low[u]);
		}
		else if (instk[j]) {
			low[u] = min(low[u], dfn[j]);
		}
	}
	if (dfn[u] == low[u]) {
		scc_cnt++;
		int y;
		do {
			y = stk[top--];
			instk[y] = 0;
			siz[scc_cnt]++;
			id[y] = scc_cnt;
		} while (y != u);
	}
}

int main() {
	cin >> n;
	memset(h, -1, sizeof h);
	for (int i = 1, a; i <= n; i++) {
		while (cin >> a, a) {
			add(i, a);
		}
	}
	for (int i = 1; i <= n; i++) {
		if (!dfn[i])
			tarjan(i);
	}
	int q = 0, p = 0;
	for (int i = 1; i <= n; i++) {
		for (int j = h[i]; j != -1; j = ne[j]) {
			int k = e[j];
			if (id[i] != id[k]) {
				dout[id[i]]++;
				din[id[k]]++;
			}
		}
	}
	for (int i = 1; i <= scc_cnt; i++) {
		if (din[i] == 0)
			q++;
		if (dout[i] == 0)
			p++;
	}
	cout << q << endl;
	if (scc_cnt == 1)cout << 0 << endl;
	else
		cout << max(q, p) << endl;
	return 0;
}

———————————————————————————————————————2024.2.2

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<math.h>
#include<map>
#include<sstream>
#include<deque>
#include<unordered_map>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N = 1e2 + 5, M = 1e4 + 5, INF = 0x3f3f3f3f;
int n;
int h[N], e[M], ne[M], idx;
int stk[N], top;
bool instk[N];
int dfn[N], low[N], timestamp;
int id[N], scc_cnt;
int dout[N], din[N];

void add(int a, int b) {
	e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

void tarjan(int u) {
	dfn[u] = low[u] = ++timestamp;
	stk[++top]=u, instk[u] = 1;
	for (int i = h[u]; i != -1; i = ne[i]) {
		int j = e[i];
		if (!dfn[j]) {
			tarjan(j);
			low[u] = min(low[u], low[j]);
		}
		else if (instk[j])low[u] = min(low[u], dfn[j]);
	}
	if (dfn[u] == low[u]) {
		int y;
		scc_cnt++;
		do {
			y = stk[top--];
			instk[y] = 0;
			id[y] = scc_cnt;
		} while (y != u);
	}
}

int main() {
	cin >> n;
	memset(h, -1, sizeof h);
	for (int i = 1,a; i <= n; i++) {
		while (cin >> a, a)add(i, a);
	}
	for (int i = 1; i <= n; i++) {
		if (!dfn[i])
			tarjan(i);
	}
	for (int i = 1; i <= n; i++) {
		for (int j = h[i]; j != -1; j = ne[j]) {
			int k = e[j];
			if (id[k] != id[i]) {
				din[id[k]]++;
				dout[id[i]]++;
			}
		}
	}
	int ans1 = 0, ans2 = 0;
	for (int i = 1; i <= scc_cnt; i++) {
		if (!din[i])ans1++;
		if (!dout[i])ans2++;
	}
	cout << ans1 << endl;
	if (scc_cnt == 1)cout << 0 << endl;
	else {
		cout << max(ans1, ans2) << endl;
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值