算法笔记-强连通分量

T a n j a n Tanjan Tanjan算法基于对图的深度优先搜索,每个强连通分量是深度优先搜索树的一棵子树。搜索时,将发现的节点加入栈,栈中的某一结点到栈顶的结点构成一个强连通分量。
定义 d f n ( u ) dfn(u) dfn(u)为发现 u u u的时间, l o w ( u ) low(u) low(u) u u u及其子树能够追溯到的最早的栈中结点的 d f n dfn dfn
在回溯的时候,如果有 d f n ( u ) = l o w ( u ) dfn(u)=low(u) dfn(u)=low(u),则结点 u u u到栈顶的结点构成强连通分量,将该强连通分量出栈。

void tarjan(int u){
	dfn[u] = low[u] =++cnt;
	instack[u] = 1;
	stack[++top] = u;
	for(int i = head[u]; i; i = e[i].nxt){
		int v = e[i].to;
		if(!dfn[v]){
			tarjan(v);
			low[u] = min(low[u], low[v]);
		}
		else if(instack[v])
			low[u] = min(low[u], dfn[v]);
	}
	if(dfn[u] == low[u]){
		++scc;
		while(stack[top+1] != u){
			int x = stack[top];
			belong[x] = scc;
			siz[scc] += 1;
			top--;
			instack[x] = 0;
		}
	}
}


间谍网络

题意:

给定一个有向图,某些点有点权,支付点权的代价后可以到达 (空降) 到这个点。在一个点,可以到达指向的点。选择一些结点支付点权,求使图联通的最小代价。

解析:

对于在同一个强连通分量中的点,支付任意一点的点权,都可以到达连通分量中的其他点,所以要支付最小的。所以将有向图缩点,构建新的DAG图,缩点的同时记录到达(空降)改强连通分量的最小代价。在新图上,如果一个点的入度为零,这个点只能支付代价空降;如果入度不为零,该点可以由其他结点到达。

代码:
#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;
const int INF = 0x3f3f3f3f;
struct edge{
	int to, nxt;
}e[maxn<<1];
int head[maxn], tot;
int dfn[maxn], low[maxn], cnt;
int scc, siz[maxn];
int instack[maxn], stack[maxn], top;
int belong[maxn];
int cost[maxn], sum[maxn];
int rd[maxn];
int n, p, r;
int ans;
void add(int a, int b){
	e[++tot].nxt = head[a];
	e[tot].to = b;
	head[a] = tot;
}
void tarjan(int u){
	dfn[u] = low[u] =++cnt;
	instack[u] = 1;
	stack[++top] = u;
	for(int i = head[u]; i; i = e[i].nxt){
		int v = e[i].to;
		if(!dfn[v]){
			tarjan(v);
			low[u] = min(low[u], low[v]);
		}
		else if(instack[v])
			low[u] = min(low[u], dfn[v]);
	}
	if(dfn[u] == low[u]){
		++scc;
		while(stack[top+1] != u){
			int x = stack[top];
			belong[x] = scc;
			siz[scc] += 1;
			sum[scc] = min(sum[scc], cost[x]);
			top--;
			instack[x] = 0;
		}
	}
}
int main(){
	cin >> n >> p;
	memset(cost, INF, sizeof(cost));
	memset(sum, INF, sizeof(sum));
	for(int i = 1; i <= p; i++){
		int x, money;
		cin >> x >> money;
		cost[x] = money;
	}
	cin >> r;
	for(int i = 1; i <= r; i++){
		int u, v;
		cin >> u >> v;
		add(u, v);
	}
	for(int i = 1; i <= n; i++){
		if(!dfn[i]&&cost[i]!=INF)
			tarjan(i);
	}
	for(int i = 1; i <= n; i++){
		if(!dfn[i]){
			printf("NO\n%d", i);
			return 0;
		}
	}
	for(int k = 1; k <= n; k++){
		for(int i = head[k]; i; i = e[i].nxt){
			int v = e[i].to;
			if(belong[v] != belong[k]){
				rd[belong[v]]++;
			}
		}
	}
	for(int i = 1; i <= scc; i++){
		if(!rd[i])
			ans += sum[i];
	}
	printf("YES\n%d", ans);
	return 0;
}


稳定婚姻

解析:

如果一对婚姻是不安全的,夫妻一定会处于一个环中。所以,夫妻之间相连,情人之间相连。为了构成有向图,夫妻之间女连男,情人之间男连女。

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 8e3+10;
const int maxm = 4e4+10;
map<string, int> mp;
int head[maxn], tot;
struct edge{
	int to, nxt; 
}e[maxm<<1];
void add(int a, int b){
	e[++tot].nxt = head[a];
	e[tot].to = b;
	head[a] = tot;
}
int n, m, id;
int dfn[maxn], low[maxn], cnt;
int s[maxn], instack[maxn], top;
int belong[maxn], scc, siz[maxn], rd[maxn];
void tarjan(int u){
	dfn[u] = low[u] = ++cnt;
	s[++top] = u; instack[u] = 1;
	for(int i = head[u]; i; i = e[i].nxt){
		int v = e[i].to;
		if(!dfn[v]){
			tarjan(v);
			low[u] = min(low[u], low[v]);
		}
		else if(instack[v])
			low[u] = min(low[u], dfn[v]);
	}
	if(dfn[u] == low[u]){
		++scc;
		while(s[top+1] != u){
			int x = s[top];
			belong[x] = scc;
			siz[scc]++;
			top--;
			instack[x] = 0;
		}
	}
}
int main(){
	cin >> n;
	string girl, boy;
	for(int i = 1; i <= n; i++){
		cin >> girl >> boy;
		mp[girl] = ++id;
		mp[boy] = id+n;
		add(mp[girl], mp[boy]);
	}
	cin >> m;
	for(int i = 1; i <= m; i++){
		cin >> girl >> boy;
		add(mp[boy], mp[girl]);
	}
	for(int i = 1; i <= 2*n; i++)
		if(!dfn[i])
			tarjan(i);
	for(int i = 1; i <= n; i++){
		if(belong[i] != belong[i+n]){
			printf("Safe\n");	
		}
		else{
			printf("Unsafe\n");
		}
	}
	return 0;
}

P2341 [USACO03FALL / HAOI2006] 受欢迎的牛 G

题意:

牛群中有的牛喜欢其他牛,喜欢具有传递性。被所有牛喜欢的牛是明星牛,询问明星牛的数目

解析:

缩点,变成DAG。判断出度为0的结点是否只有一个;如果只有一个,则该强连通分量中的所有牛都是明星牛;否则没有明星牛

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值