1. 题面梗概
中译中 : 半连通子图:子图中任意两点 u , v u, v u , v ,要么 u → v u \to v u → v ,要么 v → u v \to u v → u 。现在给你一个有向图,求节点数最多的半连通子图的节点数 和 方案数 。
2. 逻辑推导
有向图 + 连通图 = SCC。那么半连通跟强连通有什么关系呢?
不难发现,强连通分量肯定是半连通图,我们先考虑缩点。
2.1 DAG
缩完点后图就变成了DAG,接着我们观察半连通在图上的映射——在简单的对比样例后发现,这是一条单项延伸的链。
所以,题目要求的 最大半连通子图 ,其实就等价于在 DAG 上找一条节点最多的链 。
2.2 拓扑 DP
既然要找最多的点和方案数,那么很显然就是 DP 的思维了,而且还是 DAG 上的DP,直接锁定模型——拓扑排序。
我们需要维护两个状态:节点数 / 方案数。
f[i] 表示到达缩点 i i i 的最长链的节点数。
dp[i] 表示到达缩点 i i i 且节点数为 f [ i ] f[i] f [ i ] 的方案数。
考虑状态转移:
若 f[u] + scc_size[v] > f[v],说明这条链更长,更新 f[v] = f[u],dp[v] = dp[u]。
若 f[u] + scc_size[v] == f[v],累计方案数 dp[v] += dp[u]。
2.3 去重边
虽然重边不会影响最长链,但是会严重干扰方案数,所以必须去重边!
这里我偷懒直接在新图上去重,当然也有别的方法,比如把全部边都存下来,去重完再建图。
2.3 结论
这是道 SCC + 拓扑DP 的好题。
3. 代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 #include <bits/stdc++.h> using namespace std;using ll = long long ;const int maxn = 1e5 + 50 ;const int maxm = 1e6 + 50 ;struct Edge { int u, v; } edges[maxm]; vector< int > adj[maxn], scc_adj[maxn]; stack< int > st;int dfn[maxn], low[maxn], timer;bool in_stack[maxn];int scc[maxn], scc_cnt, scc_size[maxn];int n, m, mod;int indeg[maxn]; ll f[maxn], dp[maxn];void tarjan ( int u ) { dfn[u] = low[u] = ++ timer; st.push ( u ); in_stack[u] = 1 ; for ( int v : adj[u] ) { if ( !dfn[v] ) { tarjan ( v ); low[u] = min ( low[u], low[v] ); } else if ( in_stack[v] ) { low[u] = min ( low[u], dfn[v] ); } } if ( low[u] == dfn[u] ) { scc_cnt ++; int v; do { v = st.top ( ); st.pop ( ); in_stack[v] = 0 ; scc[v] = scc_cnt; scc_size[scc_cnt] ++; } while ( u != v ); } }void solve ( ) { cin >> n >> m >> mod; for ( int i = 1 ; i <= m; ++ i ) { int u, v; cin >> u >> v; adj[u].push_back ( v ); edges[i] = { u, v }; } for ( int i = 1 ; i <= n; ++ i ) { if ( !dfn[i] ) tarjan ( i ); } for ( int i = 1 ; i <= m; ++ i ) { auto [u, v] = edges[i]; if ( scc[u] == scc[v] ) continue ; scc_adj[scc[u]].push_back ( scc[v] ); } for ( int i = 1 ; i <= scc_cnt; ++ i ) { sort ( scc_adj[i].begin ( ), scc_adj[i].end ( ) ); scc_adj[i].erase ( unique ( scc_adj[i].begin ( ), scc_adj[i].end ( ) ), scc_adj[i].end ( ) ); for ( int v : scc_adj[i] ) indeg[v] ++; } queue< int > q; for ( int i = 1 ; i <= scc_cnt; ++ i ) { if ( indeg[i] == 0 ) { q.push ( i ); dp[i] = 1 ; f[i] = scc_size[i]; } } while ( !q.empty ( ) ) { int u = q.front ( ); q.pop ( ); for ( int v : scc_adj[u] ) { indeg[v] --; if ( f[u] + scc_size[v] > f[v] ) { f[v] = f[u] + scc_size[v]; dp[v] = dp[u]; } else if ( f[u] + scc_size[v] == f[v] ) { dp[v] = ( dp[v] + dp[u] ) % mod; } if ( indeg[v] == 0 ) q.push ( v ); } } ll ans_k = 0 , ans_c = 0 ; for ( int i = 1 ; i <= scc_cnt; ++ i ) { if ( f[i] > ans_k ) ans_k = f[i]; } for ( int i = 1 ; i <= scc_cnt; ++ i ) { if ( f[i] == ans_k ) ans_c = ( ans_c + dp[i] ) % mod; } cout << ans_k << '\n' << ans_c << '\n' ; }int main ( ) { ios::sync_with_stdio (0 ); cin.tie (0 ); cout.tie (0 ); solve ( ); return 0 ; }
4. 复盘