算法
手玩样例可以快速得知, 如果第 \(i\) 个点不是割点, 只会导致其他点 (以下设为点集 \(O\) ) 不能到达 \(i\) 点, 不会影响 \(O\) 之间的连通性
那么显然的, 我们进行分类讨论
-
\(i\) 点不是割点
显然的, 只会造成 \(2(n - 1)\) 的贡献 -
\(i\) 点就是割点
这种情况稍微复杂, 我们考虑 \(\rm{Tarjan}\) 算法的本质是 \(\rm{dfs}\) , 观察到在 \(\rm{dfs}\) 树中, 割点的不同子树本质上就是分割之后的点集, 令点集的个数为 \(k\) , 第 \(i\) 个点集的大小为 \(size_i\)
点对的数量为 $ 2(n-1)+\displaystyle\sum_{i=1}^k \sum_{j=1,j\neq i}^{k}size_i \times size_j$
注意点对有序
显然的, 我们只需要 \(\mathcal{O}(n ^ 2)\) 的计算割点, \(\mathcal{O}(n ^ 2)\) 的计算贡献即可
但是最坏情况下, 计算贡献的复杂度将达到 \(n ^ 2\) , 这是不符合预期的
考虑对式子进行化简
容易观察到, \(\displaystyle\sum_{j = 1}^{k} size_j = n - size_i - 1\)
现在的答案式子为
$ 2(n-1)+\displaystyle\sum_{i=1}^k \left[size_i \times (n - size_i - 1)\right]$
代码
第一次做连通分量的题, 加油吧
计算 \(Size\) 属于一种很典的处理
#include <bits/stdc++.h>
#define int long long
const int MAXM = 5e5 + 20;
const int MAXN = 1e5 + 20;
int n, m;
class Graph_Class
{
private:
public:
struct node
{
int to, w;
int next;
} Edge[MAXM << 1];
int Edge_Cnt = 0;
int head[MAXN];
void head_init() { for (int i = 0; i <= n; i++) head[i] = -1; }
void addedge(int u, int v, int w) {
Edge[++Edge_Cnt].to = v, Edge[Edge_Cnt].w = w;
Edge[Edge_Cnt].next = head[u];
head[u] = Edge_Cnt;
}
} Graph;
class Sol_Class
{
private:
int low[MAXN], dfn[MAXN], num = 0;
bool isCut[MAXN]; // 是否为割点
int Size[MAXN];
int Ans[MAXN];
void init() {
memset(low, 0, sizeof(low));
memset(dfn, 0, sizeof(dfn));
memset(isCut, false, sizeof(isCut));
memset(Size, 0, sizeof(Size));
memset(Ans, 0, sizeof(Ans));
}
void tarjan(int Now, int fa)
{
low[Now] = dfn[Now] = ++num;
int Child_Num = 0;
Size[Now] = 1;
int fa_Size = n - 1;
int LSY = 0;
bool fa_Cut = true;
for (int i = Graph.head[Now]; ~i; i = Graph.Edge[i].next)
{
int NowTo = Graph.Edge[i].to, NowW = Graph.Edge[i].w;
/*下降边*/
if (!dfn[NowTo]) // 没访问过
{
Child_Num++;
tarjan(NowTo, Now);
Size[Now] += Size[NowTo];
LSY += Size[NowTo] * (n - Size[NowTo] - 1);
low[Now] = std::min(low[Now], low[NowTo]);
/*它被分开 (割点)*/
if(low[NowTo] >= dfn[Now] && Now != 1) {
fa_Size -= Size[NowTo];
isCut[Now] = true;
Ans[Now] += Size[NowTo] * (n - Size[NowTo] - 1);
}else fa_Cut = false;
}
/*上升边*/
else if (dfn[NowTo] < dfn[Now] && NowTo != fa) { // 记得特判父亲
low[Now] = std::min(low[Now], dfn[NowTo]);
}
}
if (Now == 1 && Child_Num >= 2) {
Ans[Now] = LSY;
}else if (Now != 1 && isCut[Now]){
Ans[Now] += fa_Size * (n - fa_Size - 1);
}
Ans[Now] += 2 * (n - 1);
}
public:
void solve() {
init();
tarjan(1, -1);
for (int i = 1; i <= n; i++) printf("%lld\n", Ans[i]);
}
} Sol;
signed main()
{
// freopen("P3469_8.in", "r", stdin);
scanf("%lld %lld", &n, &m);
Graph.head_init();
for (int i = 1; i <= m; i++) {
int u, v;
scanf("%lld %lld", &u, &v);
Graph.addedge(u, v, 0);
Graph.addedge(v, u, 0);
}
Sol.solve();
return 0;
}
/*
5 5
1 2
2 3
1 3
3 4
4 5
*/
感冒了, 代码细节比较多, 我不在这里整理
总结
注意结论的严谨性
手推样例有助于理解题意
标签:POI2008,int,割点,Blockade,BLO,Size,Now,NowTo,size From: https://www.cnblogs.com/YzaCsp/p/18551700