前置
连通分量定义:在\(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;
}