P3469 [POI 2008] BLO-Blockade
题目描述
B 城有 n n n 个城镇(从 1 1 1 到 n n n 标号)和 m m m 条双向道路。
每条道路连结两个不同的城镇,没有重复的道路,所有城镇连通。
把城镇看作节点,把道路看作边,容易发现,整个城市构成了一个无向图。
请你对于每个节点 i i i 求出,把与节点 i i i 关联的所有边去掉以后(不去掉节点 i i i 本身),无向图有多少个有序点 ( x , y ) (x,y) (x,y),满足 x x x 和 y y y 不连通。
输入格式
第一行包含两个整数 n n n 和 m m m。
接下来 m m m 行,每行包含两个整数 a a a 和 b b b,表示城镇 a a a 和 b b b 之间存在一条道路。
输出格式
输出共 n n n 行,每行输出一个整数。
第 i i i 行输出的整数表示把与节点 i i i 关联的所有边去掉以后(不去掉节点 i i i 本身),无向图有多少个有序点 ( x , y ) (x,y) (x,y),满足 x x x 和 y y y 不连通。
输入输出样例 #1
输入 #1
5 5
1 2
2 3
1 3
3 4
4 5
输出 #1
8
8
16
14
8
说明/提示
n ≤ 100000 n\le 100000 n≤100000, m ≤ 500000 m\le500000 m≤500000。
思路:
这题分析题意就是两个点
x
,
y
x,y
x,y 不在一个连通分量里面的数量有几对
设割点是
u
u
u 两棵子树
i
,
j
i, j
i,j 连着
u
u
u ,这两颗子树的去掉的不是割点的时候有序对应该是
s
i
×
(
n
−
s
i
)
s_i × (n - s_i)
si×(n−si) 。如果去掉割点的时候单独计算,割点去了之后就分出来了
m
m
m 个连通块,除了每棵子树(现在是连通块),
s
i
×
(
n
−
s
i
)
s_i × (n - s_i)
si×(n−si)还要单独判断割点被分离出来的(不在任何子树中的节点),也就是 (所有点 - 所有子树的和 - 割点) * 所有子树的和
(
n
−
∑
i
=
1
k
s
i
−
1
)
×
∑
i
=
1
k
s
i
(n-\sum_{i = 1}^{k}s_{i}-1)×\sum_{i = 1}^{k}s_{i}
(n−∑i=1ksi−1)×∑i=1ksi。
n
−
∑
i
=
1
k
s
i
−
1
n-\sum_{i = 1}^{k}s_{i}-1
n−∑i=1ksi−1这一部分求得是不在任何子树中的节点,也就是孤点。然后就是割点与这些孤点也能形成有序对,与子树形成的
s
i
×
(
n
−
s
i
)
s_i × (n - s_i)
si×(n−si) 这里计算过了,剩下的就是与孤点的,也就是
1
×
(
n
−
∑
i
=
1
k
s
i
−
1
)
1 × (n-\sum_{i = 1}^{k}s_{i}-1)
1×(n−∑i=1ksi−1)。然后就是把这两部分加起来就是割点额外产生的结果
(
n
−
∑
i
=
1
k
s
i
−
1
)
×
∑
i
=
1
k
s
i
+
(
n
−
∑
i
=
1
k
s
i
−
1
)
(n-\sum_{i = 1}^{k}s_{i}-1)×\sum_{i = 1}^{k}s_{i}+(n-\sum_{i = 1}^{k}s_{i}-1)
(n−∑i=1ksi−1)×∑i=1ksi+(n−∑i=1ksi−1), 变形
(
n
−
∑
i
=
1
k
s
i
−
1
)
×
(
∑
i
=
1
k
s
i
+
1
)
(n-\sum_{i = 1}^{k}s_{i}-1)×(\sum_{i = 1}^{k}s_{i}+1)
(n−∑i=1ksi−1)×(∑i=1ksi+1)。
举个例子:
初始图:
A --- B --- C --- E
|
D --- F
|
G
以割点B为例去掉边后的情况:
A(孤点)
C --- E(子树)
D --- F(子树)
|
G
以割点D为例去掉边后的情况:
A --- B --- C --- E(子树)
F(孤点)
G(孤点)
总结一下:
删除的不是割点:
2
×
(
n
−
1
)
2 × (n - 1)
2×(n−1)
删除的是割点:
∑
i
=
1
k
s
i
×
(
n
−
s
i
)
+
(
n
−
∑
i
=
1
k
s
i
−
1
)
×
(
∑
i
=
1
k
s
i
+
1
)
\sum_{i = 1}^{k} s_i × (n - s_i)+(n-\sum_{i = 1}^{k}s_{i}-1)×(\sum_{i = 1}^{k}s_{i}+1)
∑i=1ksi×(n−si)+(n−∑i=1ksi−1)×(∑i=1ksi+1)
Tarjan AC code:
#include <iostream>
#include <climits>
#include <limits>
#include <vector>
#include <stack>
typedef unsigned long long ull;
typedef long long ll;
typedef long double ld;
typedef std::pair<int, int> PII;
#define rep(i, n) for(int i = 0; i < n; i++)
#define Rep(i, len, n) for(int i = len; i < n; i++)
#define MAX_INT 0x7fffffff
#define MIN_INT 0x80000000
const int INF = std::numeric_limits<int>::max();
int n, m, tot = 0, root;
std::vector<std::vector<int>> e;
std::vector<int> low, dfn, cut;
std::vector<ll> size; // 存储每个子树的大小
std::vector<ll> ans; // 存储每个节点的答案
inline void Tarjan(int u, int father) {
low[u] = dfn[u] = ++tot;
size[u] = 1; // 初始化子树大小
int child = 0;
ll sum = 0; // 用于累加所有子树的大小
for(const int& v : e[u]) {
if(v == father) continue;
if(!dfn[v]) {
Tarjan(v, u);
low[u] = std::min(low[u], low[v]);
size[u] += size[v]; // 更新当前节点的子树大小
child++;
if(low[v] >= dfn[u]) {
if(u != root || child > 1) cut[u] = true;
// 计算该子树贡献的不连通点对数
ans[u] += size[v] * (n - size[v]);
sum += size[v];
}
} else {
low[u] = std::min(low[u], dfn[v]);
}
}
// 计算剩余部分的贡献
if(cut[u]) {
ans[u] += (n - 1 - sum) * (sum + 1);
// 加上根节点本身的贡献
ans[u] += (n - 1);
}
}
int main(void) {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr), std::cout.tie(nullptr);
std::cin >> n >> m;
low.resize(n + 1);
dfn.resize(n + 1);
cut.resize(n + 1);
e.resize(n + 1);
size.resize(n + 1, 0); // 初始化子树大小数组
ans.resize(n + 1, 0); // 初始化答案数组
rep(i, m) {
int a, b;
std::cin >> a >> b;
e[a].push_back(b);
e[b].push_back(a);
}
root = 1;
Tarjan(1, -1);
Rep(i, 1, n + 1) {
if(cut[i]) {
std::cout << ans[i] << '\n';
} else {
// 非割点的答案是2*(n-1)
std::cout << 2LL * (n - 1) << '\n';
}
}
return 0;
}