structEdge { int id, v; ll w; }; vector<Edge> adj[maxn];
int dfn[maxn], low[maxn], timer;
int siz[maxn]; ll ans[maxn]; int n;
voidadd_edge( int u, int v, ll w, int id ){ adj[u].push_back({ id, v, w }); adj[v].push_back({ id, u, w }); }
voidtarjan( int u, int in_edge ){ dfn[u] = low[u] = ++ timer; siz[u] = 1; ll sum = 0; ll N = n - 1; for( auto [id, v, w] : adj[u] ) { if( !dfn[v] ) { tarjan( v, id ); siz[u] += siz[v]; low[u] = min( low[u], low[v] ); if( low[v] >= dfn[u] ) { ans[u] += siz[v] * ( N - siz[v] ); sum += siz[v]; } } elseif( id != in_edge ) { low[u] = min( low[u], dfn[v] ); } } ll rem = N - sum; ans[u] += rem * ( N - rem ); ans[u] += 2 * N; }
voidsolve( ){ n = read( ); int m = read( ); for( int i = 1; i <= m; ++ i ) { int u = read( ), v = read( ); add_edge( u, v, 0, i ); } for( int i = 1; i <= n; ++ i ) { if( !dfn[i] ) tarjan( i, 0 ); } for( int i = 1; i <= n; ++ i ) { printf( "%lld\n", ans[i] ); } }
voiddfs_tree( int u, int p ){ fa[u] = p, siz[u] = ( u <= n ); // 圆点 1 | 方点 0 for( int v : tree[u] ) { if( v == p ) continue; dfs_tree( v, u ); siz[u] += siz[v]; } }
voidcalc( int u ){ ans[u] = 2ll * ( n - 1 ); for( int v : tree[u] ) { ll s = 0; if( fa[v] == u ) s = siz[v]; else s = n - siz[u]; ans[u] += s * ( n - 1 - s ); } }
voidsolve( ){ n = read( ); int m = read( ); for( int i = 1; i <= m; ++ i ) { int u = read( ), v = read( ); add_edge( u, v, 0, i ); }
for( int i = 1; i <= n; ++ i ) { if( !dfn[i] ) top = 0, tarjan( i, 0 ); }
for( int id = 1; id <= vcnt; ++ id ) { int block = n + id; for( int u : comp[id] ) { tree[block].push_back( u ); tree[u].push_back( block ); } }
dfs_tree( 1, 0 ); for( int u = 1; u <= n; ++ u ) { calc( u ); cout << ans[u] << '\n'; } }
两种做法的关系
两种做法本质上处理的是同一组“删除节点后产生的连通块”。
Tarjan 直接统计的方法利用 low[v]≥dfn[u],在原图 DFS 树中直接识别删除 u 后独立出来的连通块,其大小就是 siz[v]。这种做法不显式保存点双结构,因此代码更短、常数也更小。