Blockade(tarjan求割点...)-poi2008
题意:n个点,m条边双向连通,无重边,自环;输出n个数,代表把第i个点去掉后,有多少访问不能发生a->b ≠b->a;
解:对于一个点有两种情况:
1,非割点:结果(n-1)*2,这个点不能到别的店,别的点也不能到这个点;
2,割点。
判断一个点是否为割点
1,对于根节点,计算其子树数量,如果有两颗以上的子树,就是割点,去掉这个点,两颗子树不会互达;
2,对于非根节点,tarjan求。维护两个数组dfn[],low[],dfn[u]时间戳,low[u]顶点u及其子树中的点,通过非父子边,能够回溯到的最早的点(dfn最小)。
对于边(u,v),如果dfn【u】<=low【v】,u就是割点,其他类似tarjan求连通分量
割点状况:
1》,以u为根的子树对外界的来往
设子树数量为ss,对外界n-1+n-1
2》,外界对以u为根子树的来往
(n-1-ss)*(ss+1)其他点到这颗子树上的点
即:ans【u】+=(n-1-ss)*(ss+1)+(n-1)
/*size[u]表示以u为根的子树有多少节点
isok[u],判断u是否为割点*/
vector存图会超内存,用链式前向星存图
代码:
#include<bits/stdc++.h> using namespace std; const int maxn=1e5+10; const int mod=142857; const int inf=0x3f3f3f3f; typedef long long ll; typedef pair<int,int> pii; const int N=5e5+10; int n,m; struct edge { int v , next ; }e[N<<1] ; int tot,last[N] ; inline void add(int u,int v) { e[++tot]=(edge){v,last[u]}; last[u]=tot ; e[++tot]=(edge){u,last[v]}; last[v]=tot ; } ll ans[maxn]; int idx,dfn[maxn],low[maxn]; int size[maxn]; bool isok[maxn]; void tarjan(int u) { dfn[u]=low[u]=++idx; size[u]=1; int cnt=0,ss=0; for ( int i = last[u] ; i != -1 ; i = e[i].next ) { int v = e[i].v ; if (!dfn[v]) { tarjan( v ) ; size[u] += size[v] ; low[u] = min ( low[u] , low[v] ) ; if ( dfn[u] <= low[v] ) { cnt ++ ; ss += size[v] ; ans[u] += (ll)size[v] * ( n - size[v] ) ; if ( u != 1 || cnt > 1 ) isok[u] = 1 ; } } else low[u] = min ( low[u] , dfn[v] ) ; } if(!isok[u]) ans[u]=(n-1)*2; else ans[u]+= (ll)(n-ss-1)*(ss+1)+(n-1); } int main() { while(cin>>n>>m) { memset(last,-1,sizeof(last)); memset(isok,0,sizeof(isok)); for(int i=1; i<=n; i++) dfn[i]=low[i]=0; tot=idx=0; for(int i=1; i<=m; i++) { int u,v; cin>>u>>v; add(u,v); } tarjan(1); for(int i=1; i<=n; i++) printf("%lld\n",ans[i]); } }