有向图的强连通分量
前置
连通分量定义:在\(u\),\(v\)在一个强连通分量中,则存在\(u\)到\(v\)的路径和\(v\)到\(u\)的路径。
强连通分量scc:极大连通分量
作用:通过缩点将有向图转换为有向无环图DAG(拓扑图),将题目变得好做。
边:树枝边、前向边、后向边、横叉边
判断一个点是否在某个scc中:
- 存在一条后向边指向祖先节点。
- 先走到横叉边,横叉边再走到祖先节点。
注意:对于前向边不会成为判断形成回路的关键。
tarjan算法求强连通分量SCC
对于每个点定义两个时间戳:\(dfn[u]\)表示遍历到u的时间,\(low[u]\)表示从\(u\)开始走所能遍历到的最小时间戳。
u是其所在的强连通分量的最高点,等价于\(dfn[u] == low[u]\)
连通分量编号递减的顺序一定是拓扑序。
受欢迎的牛
#include <bits/stdc++.h>
using namespace std;
const int N = 10005, M = 50005;
int n, m, head[N], cnt = -1, tot = 0, dfn[N], low[N];
int scc_cnt = 0, id[N], out[N], siz[N];
stack<int> stk;
bool in_stk[N];
struct edge
{
int to, nxt;
}e[M];
void add_edge(int u, int v)
{
e[++ cnt].to = v, e[cnt].nxt = head[u], head[u] = cnt;
return ;
}
void tarjan(int x)
{
dfn[x] = low[x] = ++ tot, stk.push(x), in_stk[x] = true;
for (int i = head[x]; i != -1; i = e[i].nxt)
{
int y = e[i].to;
if(!dfn[y])
{
tarjan(y);
low[x] = min(low[x], low[y]);
}
else if(in_stk[y]) low[x] = min(low[x], dfn[y]);
}
if(low[x] == dfn[x])
{
int y = 0;
scc_cnt ++;
do
{
y = stk.top(), stk.pop();
siz[scc_cnt] ++;
id[y] = scc_cnt;
in_stk[y] = false;
}while(y != x);
}
return ;
}
void solve()
{
// printf("%d %d!\n", scc_cnt, siz[1]);
for (int x = 1; x <= n; ++x)
{
for (int j = head[x]; j != - 1; j = e[j].nxt)
{
int y = e[j].to;
if(id[x] != id[y]) out[id[x]] ++;
}
}
int c = 0, ans = 0;
for (int i = 1; i <= scc_cnt; ++ i)
{
if(!out[i]) c ++, ans = siz[i];
}
if(c == 1) printf("%d", ans);
else printf("0");
return ;
}
int main()
{
memset(head, -1, sizeof(head));
scanf("%d %d", &n, &m);
for (int i = 1; i <= m; ++ i)
{
int u, v;
scanf("%d %d", &u, &v);
add_edge(u, v);
}
for (int i = 1; i <= n; ++ i) if(!dfn[i]) tarjan(i);
solve();
}
学校网络
#include <bits/stdc++.h>
using namespace std;
const int N = 105, M = N * N;
struct edge
{
int to, nxt;
}e[M];
int head[N], cnt = -1;
int n, dfn[N], low[N], timestamp = 0, scc_cnt = 0, id[N], out[N];
stack<int> stk;
bool in_stk[N];
void add_edge(int u, int v)
{
e[++ cnt].to = v, e[cnt].nxt = head[u], head[u] = cnt;
return ;
}
void tarjan(int x)
{
low[x] = dfn[x] = ++ timestamp;
stk.push(x), in_stk[x] = true;
for (int i = head[x]; i != -1; i = e[i].nxt)
{
int y = e[i].to;
if(!dfn[y])
{
tarjan(y);
low[x] = min(low[x], low[y]);
}
else if(in_stk[y]) low[x] = min(low[x], dfn[y]);
}
if(low[x] == dfn[x])
{
int y;
scc_cnt ++;
do
{
y = stk.top(), stk.pop();
id[y] = scc_cnt;
in_stk[y] = false;
}while(y != x);
}
return ;
}
int in[N];
void solve()
{
for (int x = 1; x <= n; ++x)
{
for (int i = head[x]; i != -1; i = e[i].nxt)
{
int y = e[i].to;
if(id[x] != id[y]) in[id[y]] ++, out[id[x]] ++;
}
}
int sum = 0, tot = 0;
for (int i = 1; i <= scc_cnt; ++ i)
{
if(!in[i]) sum ++;
if(!out[i]) tot ++;
}
printf("%d\n", sum);
if(scc_cnt != 1) printf("%d", max(tot, sum));
else printf("0");
return ;
}
int main()
{
memset(head, -1, sizeof head);
scanf("%d", &n);
for (int i = 1; i <= n; ++ i)
{
int x;
while(true)
{
scanf("%d", &x);
if(x == 0) break;
add_edge(i, x);
}
}
for (int i = 1; i <= n; ++ i) if(!dfn[i]) tarjan(i);
solve();
return 0;
}
最大半连通子图
#include <bits/stdc++.h>
using namespace std;
const int N = 100005, M = 1000005;
int n, m, p, cnt = -1, timestamp = 0, scc_cnt = 0, low[N], dfn[N], f[N], g[N], head[N], siz[N], id[N];
int in[N];
stack<int> stk;
bool in_stk[N], vis[N];
struct edge
{
int to, nxt;
}e[M];
void add_edge(int u, int v)
{
e[++ cnt].nxt = head[u], e[cnt].to = v, head[u] = cnt;
return ;
}
vector<int> G[N];
void tarjan(int x)
{
low[x] = dfn[x] = ++ timestamp, stk.push(x), in_stk[x] = true;
for (int i = head[x]; i != -1; i = e[i].nxt)
{
int y = e[i].to;
if(!dfn[y])
{
tarjan(y);
low[x] = min(low[x], low[y]);
}
else if(in_stk[y]) low[x] = min(low[x], dfn[y]);
}
if(low[x] == dfn[x])
{
int y;
scc_cnt ++;
do
{
y = stk.top(), stk.pop();
in_stk[y] = false;
siz[scc_cnt]++, id[y] = scc_cnt;
}while(y != x);
}
return ;
}
void build()
{
for (int x = 1; x <= n; ++ x)
{
for (int j = head[x]; j != -1; j = e[j].nxt)
{
int y = e[j].to;
if(id[x] != id[y]) G[id[x]].push_back(id[y]), in[id[y]] ++ ;
}
}
return ;
}
int main()
{
scanf("%d %d %d", &n, &m, &p);
for (int i = 0; i <= n; ++ i) head[i] = -1;
while(m --)
{
int u, v;
scanf("%d %d", &u, &v);
add_edge(u, v);
}
for (int i = 1; i <= n; ++ i) if(!dfn[i]) tarjan(i);
build();
int ans = 0, sum = 0;
for(int x = scc_cnt; x >= 1; -- x)
{
if(!in[x]) g[x] = 1;
for (int j = 0; j < G[x].size(); ++ j)
{
int y = G[x][j];
f[y] = max(f[y], f[x] + siz[x]);
}
}
for (int x = scc_cnt; x >= 1; -- x)
{
ans = max(ans, f[x] + siz[x]);
for (int j = 0; j < G[x].size(); ++ j)
{
int y = G[x][j];
if(f[y] == f[x] + siz[x] && !vis[y]) vis[y] = 1, g[y] = (g[y] + g[x]) % p;
}
for (int j = 0; j < G[x].size(); ++ j) vis[G[x][j]] = 0;
}
for (int x = scc_cnt; x >= 1; -- x)
{
if(ans == f[x] + siz[x]) sum = (sum + g[x]) % p;
}
printf("%d\n%d", ans, sum);
return 0;
}
银河
这个题可以不用差分s约束里的spfa来判断,是因为这个图具有一定的特殊性,所有的边的权值都是正数->能用强连通分量保证稳定的线性时间复杂度。
问题就转化成了在拓扑图上面找 到一个点的最长路,然后累加就可以了。
/*
T = 1 A == B
T = 2 A < B
T = 3 A >= B
T = 4 A > B
T = 5 A <= B
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 100005, M = 300005;
typedef long long ll;
ll ans = 0;
struct edge1
{
int to, nxt, w;
}e[M];
struct edge2
{
int v, w;
};
vector<edge2> G[N];
int n, m, cnt = -1, timestamp = 0, scc_cnt = 0, head[N];
int low[N], dfn[N], id[N], in[N], siz[N];
stack<int> stk;
bool in_stk[N], vis[N];
void add_edge(int u, int v, int w)
{
e[++ cnt].to = v, e[cnt].nxt = head[u], e[cnt].w = w, head[u] = cnt;
return ;
}
void tarjan(int x)
{
low[x] = dfn[x] = ++timestamp, in_stk[x] = true, stk.push(x);
for (int i = head[x]; i != -1; i = e[i].nxt)
{
int y = e[i].to;
if(!dfn[y])
{
tarjan(y);
low[x] = min(low[x], low[y]);
}
else if(in_stk[y]) low[x] = min(low[x], dfn[y]);
}
if(low[x] == dfn[x])
{
int y;
scc_cnt ++;
do
{
y = stk.top(), stk.pop();
id[y] = scc_cnt, siz[scc_cnt] ++;
in_stk[y] = false;
}while(y != x);
}
return ;
}
bool judge()
{
bool pd = true;
for (int x = 1; x <= n; ++ x)
{
for (int j = head[x]; j != - 1; j = e[j].nxt)
{
int y = e[j].to;
if(id[x] == id[y] && e[j].w) {pd = false; break; }
else if(id[x] != id[y]) G[id[x]].push_back((edge2){id[y], e[j].w}), in[id[y]] ++;
}
}
return pd;
}
int dp[N];
int main()
{
scanf("%d %d", &n, &m);
for (int i = 0; i <= n; ++ i) head[i] = -1;
while(m --)
{
int T, u, v;
scanf("%d %d %d", &T, &u, &v);
if(T == 1) add_edge(u, v, 0), add_edge(v, u, 0);
else if(T == 2) add_edge(u, v, 1);
else if(T == 3) add_edge(v, u, 0);
else if(T == 4) add_edge(v, u, 1);
else if(T == 5) add_edge(u, v, 0);
}
for (int i = 1; i <= n; ++ i) if(!dfn[i]) tarjan(i);
if(judge() == false)
{
printf("-1");
}
else
{
for (int i = 1; i <= scc_cnt; ++ i)
{
if(!in[i]) G[scc_cnt + 1].push_back((edge2){i, 1});
}
for (int x = scc_cnt + 1; x >= 1; -- x)
{
for (int j = 0; j < G[x].size(); ++ j)
{
int y = G[x][j].v, w = G[x][j].w;
dp[y] = max(dp[y], dp[x] + w);
}
}
for (int x = scc_cnt; x >= 1; -- x) ans = ans + (ll)dp[x] * siz[x];
printf("%lld", ans);
}
return 0;
}

浙公网安备 33010602011771号