思路
I.设在搜索树T中以x为根的树包含的点集为SubTree(x)。
II.这里去除割点可以理解为删除与该点相连的所有边。
III.这里提到的连通块是指当某一割点去除时:1.其中任何两个点都能相互到达 2.没有更大的连通块包含该块 当然,根据II,我们把单独的X也看做一个连通块
IV.为了方便,我们直接将(x,y)(满足x不能到y)成为“点对”
V. ~S:S的补集,A^B:在A中不包括点B的所有点构成的集合
利用Tarjan查找割点的同时,我们可以找出该割点X去除后剩余的连通块(有两种情况,一种是在SubTree(X)^X中,另一种是~SubTree(X))。
只要能够理解割点的求解过程,这还是很好理解的,这里不再赘述。
然后要求“点对”。
对于一个点X,不管是否为割点,点对(i,j)为“点对”,当且仅当
i != j且i、j属于两个不同的连通块
根据定义,很容易证明这个推论。
代码有多种写法,这里选取我能想到的最简单的写法。
在枚举连通块时,ans加上s[to[i]] * ( n - s[to[i]] ),即一次性处理一个连通块的所有点,它们与其他不属于这个连通块的点都构成“点对”。当然,别忘了X与~SubTree(X)。
代码
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define MAXN 100005
#define MAXM 1000005
int n, m;
int hd[MAXN], nxt[MAXM], to[MAXM], tot;
int dfn[MAXN], low[MAXN], root, num;
LL ans[MAXN];
int s[MAXN];
void Add( int x, int y ){ nxt[++tot] = hd[x]; hd[x] = tot; to[tot] = y; }
void DFS( int x ){
s[x] = 1;
low[x] = dfn[x] = ++num;
LL b(0);
for ( int i = hd[x]; i; i = nxt[i] ){
if ( !dfn[to[i]] ){
DFS( to[i] ); s[x] += s[to[i]];
low[x] = min( low[x], low[to[i]] );
if ( dfn[x] <= low[to[i]] ) ans[x] += (long long)s[to[i]] * ( n - s[to[i]] ), b += s[to[i]];//发现新的连通块!
} else low[x] = min( low[x], dfn[to[i]] );
}
ans[x] += (long long)( n - b - 1 ) * ( b + 1 ) + ( n - 1 );//算上~SubTree(X)与X
}
int main(){
scanf( "%d%d", &n, &m );
for ( int i = 1; i <= m; ++i ){
int x, y; scanf( "%d%d", &x, &y ); Add( x, y ); Add( y, x );
}
DFS( 1 );
for ( int i = 1; i <= n; ++i ) printf( "%lld\n", ans[i] );
return 0;
}

京公网安备 11010502036488号