【题解】P3469 [POI2008]BLO-Blockade(割点)

【题解】P3469 [POI2008]BLO-Blockade

图论割点好题!

题目链接

P3469 [POI2008]BLO-Blockade - 洛谷

题意概述

给定一张无向图,求每个点被封锁之后有多少个有序点对 \((x,y)(x \ne y,1 \le x,y \le n)\) 满足 \(x\) 无法到达 \(y\)

思路分析

首先需要说明一个易错点,即有序点对,即 \((x,y)\)\((y,x)\) 算作不同的点对,也就是计数的时候要算两遍。

显然答案的数量与当前节点是否为割点有关。

那么我们分类讨论,设原图共有 \(n\) 个节点,对于当前节点 \(i\)

  • 若节点 \(i\) 不是割点,则把节点 \(i\) 关联的所有边删掉后,只有节点 \(i\) 与其它 \(n-1\) 个节点不连通,其它 \(n-1\) 个节点间还是互相连通的。

    所以答案是 \(2(n-1)\)

  • 若节点 \(i\) 是割点,则把节点 \(i\) 关联的所有边删掉后,图会分裂为多个连通块。

    显然任意两个连通块的节点间都不能互相到达。

    根据乘法原理加法原理,设有 \(k\) 个连通块,且第 \(i\) 个连通块的大小是 \(siz_i\),则每一个连通块对答案的贡献就是

    \[ans_i=\sum \limits_{j=1}^k(j \ne i)siz_i \times siz_j。 \]

    那么总的答案就是

    \[\sum \limits_{i=1}^k ans_i=\sum \limits_{i=1}^k \sum \limits_{j=1}^k (j\ne i)siz_i \times siz_j \]

    现在问题就转化为要求删掉节点 \(i\) 关联的所有边后,原图分裂为多少个连通块,以及每个连通块的大小是多少。

    一个小 trick 就是,在图论的连通性问题上,我们经常从 dfs 树的角度来分析。

    假设在 dfs 树上,有 \(t\) 个点 \(s_1,s_2,s_3,\cdots,s_t\) 满足 \(low_{s_k} \ge dfn_i\),那么在删掉节点 \(i\) 相连的所有边后,无向图至多分成 \(t+2\) 个连通块:

    • 节点 \(i\) 单独构成一个连通块;

    • \(t\) 个连通块,分别由 dfs 树上以 \(s_k(1\le k \le t)\) 为根的子树中的节点构成;

    • 还可能有一个连通块由除上述之外的其它所有节点构成。

    这块可能相对抽象,画个图来理解一下:

    如图,dfs 树上 3,5 这两个节点的 \(low\)\(\ge dfn_i\)

    删掉 \(i\) 相连的所有边后:

    无向图分裂为四个连通块,分别是:节点 4 本身以 3 为根的节点所在连通块以 4 为根的节点所在连通块剩下的 7,8,9 构成的连通块

    我们可以在 Tarjan 求割点时,顺便记录 dfs 树上每个节点的子树大小,设 \(sz_x\) 表示以 \(x\) 为根的子树大小。

    则每个 \(s_i\) 对答案的贡献为:\(sz_{s_i} \times(n-sz_{s_i})\)。(子树内所有点与子树外任意一点都不联通)

    所以 \(t\) 个以 \(s_i(1\le i\le t)\) 为根的子树总共对答案的贡献为:

    \[\sum \limits_{i=1}^t sz_{s_i} \times (n-sz_{s_i}) \]

    节点 \(i\) 对答案的贡献为:\(n-1\)。(\(i\) 与其它 \(n-1\) 个节点均不连通)

    剩下的其它点对答案的贡献为:

    \[(1+\sum \limits_{i=1}^t sz_{s_i})\times (n-1-\sum \limits_{i=1}^t sz_{s_i}) \]

    所以总的答案就是:

    \[\sum \limits_{i=1}^t sz_{s_i} \times (n-sz_{s_i})+(n-1)+(1+\sum \limits_{i=1}^t sz_{s_i})\times (n-1-\sum \limits_{i=1}^t sz_{s_i}) \]

易错点

有序点对意味着 \((x,y)\)\((y,x)\) 是不同的点对,要计数两次。

代码实现

//luoguP3469
#include<iostream>
#include<cstdio>
#include<cstring>
#define int long long
#define mk make_pair
using namespace std;
const int maxn=1e5+10;
const int maxm=5e5+10;
int tot;
int n,m;
int dfn[maxn],low[maxn],vis[maxn],siz[maxn],ans[maxn]; 

basic_string<pair<int,int> >edge[maxn];

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}

void dfs(int x,int pre)
{
	dfn[x]=low[x]=++tot;
	siz[x]=1;
	int ret=0,sum=0;
	for(auto nxt:edge[x])
	{
		int y=nxt.first,id=nxt.second;
		if(!dfn[y])
		{
			dfs(y,x);
			siz[x]+=siz[y];
			low[x]=min(low[x],low[y]);
			if(low[y]>=dfn[x])
			{
				ans[x]+=siz[y]*(n-siz[y]);//对于每一个满足条件的点累加其贡献。
				ret++;
				sum+=siz[y];//sum 是剩下的点的个数。
			} 
		}
		else if(id!=pre)low[x]=min(low[x],dfn[y]);
	}
	if(ret>=2||(ret==1&&pre))
	{
		vis[x]++;//将 x 标记为割点。
		ans[x]+=(n-1)+(n-sum-1)*(sum+1);//加上 x 本身和剩下的点的贡献。
	}
	return ;
}

signed main()
{
	n=read();m=read();
	for(int i=1;i<=m;i++)
	{
		int u,v;
		u=read();v=read();
		edge[u]+=mk(v,i);
		edge[v]+=mk(u,i);//无向图 
	}
	for(int i=1;i<=n;i++)
	{
		if(!dfn[i])dfs(i,0);
	}
	for(int i=1;i<=n;i++)
	{
		if(!vis[i])cout<<2*(n-1)<<'\n';//不是割点。
		else cout<<ans[i]<<'\n';//是割点。
	}
	return 0;
}
/*思路
首先分类讨论。
当 i 不为割点时,显然除 i 之外的其它点均联通,
只有 i 与剩下的 n-1 个节点之间不连通。即答案为 2*(n-1)
当 i 为割点时,显然当 i 删掉后,将其分成几个连通块。
那么答案就是联通块大小相乘再相加的结果(乘法加法原理)
*/ 
posted @ 2022-07-03 17:52  向日葵Reta  阅读(139)  评论(0)    收藏  举报