BZOJ 1123 无向图割点_Tarjan

BZOJ 1123

题意

给出一个无向图,n个点m条边,输出n个整数.第i个整数表示把节点i关联的所有边去掉后无向图中有多少个有序对(x,y),满足x和y不联通

思路

  1. 去掉非割点,那么整个图分为成了1个点和(n-1)个点组成的连通图,有序对指的是双向:(x,y),(y,x).此时答案=2*(n-1)
  2. 去掉割点.那么至少会被分成3块连通图,
  • 割点自身
  • 割点的子节点s1,s2,...可能有多个点满足low[y]>=dfn[x].那么子节点集合可能被分成多个联通块.
  • 割点上方的联通块
  1. 由于要统计有序对个数.在tarjan()遍历时就要统计每个子树的节点个数Size[]

    统计有序对个数

  • 有序对是有方向的.

    统计每个联通块出发到其余点的单向的对数

  1. 割点的子节点的联通块点集
    $Size[s_1]*(n-Size[s_1])+Size[s_2]*(n-Size[s_2]+.....)$
  2. 割点自身
    $1*(n-1)$
  3. 割点上方的联通块点集.(总数-割点-割点子节点数量)
    $(n - 1 - \Sigma_{k=1}^{t}Size[s_k])*(1+\Sigma_{k=1}^{t}Size[s_k])$
  • 这样就组成了一个割点的所有有序对.
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
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#define ll long long
using namespace std;
const int maxn = 1e5 + 10;
const int maxm = 5e5 + 10;

int head[maxn], ver[maxm << 1], next1[maxm << 1];
int dfn[maxn], low[maxn], Size[maxn];
ll ans[maxn];
bool cut[maxn];
int n, m, tot, num;

void add(int x,int y) {
ver[++tot] = y; next1[tot] = head[x]; head[x] = tot;
}

void tarjan(int x) {
dfn[x] = low[x] = ++num; Size[x] = 1;
int flag = 0, sum = 0;
for (int i = head[x]; i; i = next1[i]) {
int y = ver[i];
if (!dfn[y]) {
tarjan(y);
low[x] = min(low[x],low[y]);
Size[x] += Size[y];
if (low[y] >= dfn[x]) {
flag++;
ans[x] += (ll)Size[y] * (n - Size[y]); //子树的联通块出发的有序对个数
sum += Size[y]; //sum用来统计该节点子树的联通块大小
if (x != 1 || flag > 1) cut[x] = 1;//标记割点
}
}
else
low[x] = min(low[x],dfn[y]);
}
if (cut[x]) //求出从 根节点上面的联通块出发的有序对 和 根节点出发的有序对
ans[x] += (ll)(n - 1 - sum) * (sum + 1) + (n - 1);
else ans[x] = 2 * (n - 1); //不是割点,该点和另一个联通块相互出发,
}

int main() {
// freopen("a.txt","r",stdin);
cin >> n >> m; tot = 1;
for (int i = 1; i <= m; i++) {
int x, y; scanf("%d%d",&x,&y);
if (x == y) continue;
add(x, y); add(y,x);
}
tarjan(1);
for (int i = 1; i <= n; i++)
printf("%lld\n",ans[i]);
return 0;
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!