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的结点是否只有一个;如果只有一个,则该强连通分量中的所有牛都是明星牛;否则没有明星牛