[TK] BLO

初步分析

此题描述让我们想到Tarjan求割点,因此我们从割点的角度来探讨一下这道题.

假如我们去掉的不是一个割点,那么它实际上不会对连通性造成影响,但是根据样例可以看出来,删去这个点导致了其余点与当前点无法联通,即答案为 2 (n1).

假如我们去掉的点是一个割点,那么它必然会将整张图分成 k 部分,我们设第 i 部分的节点数是 si,那么显然 部分 i 与其他部分中的每一个节点都不连通, 假如现在在部分 i 内有一个节点 A,与它联通的节点数量等于 i 内其他节点数量,即 si1,又已知当前节点总数为 n1,那么与它不连通的节点数量即为 (n1)(si1)=nsi. i 内所有点都满足这一点,因此 i 内的不连通节点对数 Ci 必然满足

12Ci=si (nsi)

再加上删去 i 节点导致其他点与 i 不连通,对于全部不连通的节点数量 C ,有

12C=(n1)+1ikisi (nsi)

但是有一个问题,即通过上述代码得到的结果有部分数据偏小,说明我们还有未考虑到的部分.

来看一组样例

点击查看
5 5
1 2
2 3
1 3
3 4
4 5

输出:
8
8
8
8
8

答案:

8
8
16
14
8

image

根据我们的算法,删掉 3 之后,分出来两个 s=2 的连通块,但输出中间变量法发现,统计的 s 分别为 5,4,3,2,1. 这说明从 3 返回 1 失败了.

根据数据思考,发现数据有可能出现 搜索到的连通块的 low 值小于搜索点的 dfn 的情况,譬如整颗搜索树的根节点在这个连通块上. 这样的情况我们就不会去计算它的 size,那么我们需要考虑计算出这些连通块的贡献.

易得这些节点的数量等于总数量减去已搜索的节点数量,而已搜索的节点数量我们已经求出,正好是 1+1ikisi. 它们中的每一个节点与其他连通块均不联通,即单个贡献为 1+1ikisi. 由此,有

12C=(n1)+1ikisi (nsi) +[n(1+1ikisi] (1+1ikisi)

因此,本题的实质是使用Tarjan求割点并维护子树的 size.

思路实现

注意到我们在上述推论中提到的子树是指 当前点的邻边拓展得到的树,因此考虑在Tarjan时直接求出上述 size.

  1. 一个节点的 size 等于其子树之和.
  2. 一个节点的子节点全部遍历完成后,该节点的根节点再计入 size. (这其实与Tarjan的思想相一致).

同时,仿照树形DP,为了防止DFS的时候直接走回父节点,我们给Tarjan引入一个新参数 root 表示当前节点来处的父节点 ,当遍历到 root 时直接跳过.

我们引入一个变量 res 用于累计目前搜索到的当前节点的子节点的 size 总和. si 表示当前结束递归的子节点的 size,那么我们在每次遇到满足条件的点时,累计当前 si×res 的值到 ans ,这样就可以求出全部 1ikisi (nsi) 的值. 请读者稍加思考,这样可以快速求出答案,因为它保证每对点仅会乘到一次.

代码实现

点击查看
void tarjan(long long s,long long root){
    dfn[s]=low[s]=++cnt;
	long long son=0;
	long long res=0;
    for(int i:e[s]){
		if(i==root){
			continue; //防止走回去
		}
        if(!dfn[i]){
            tarjan(i,s);
			size[s]+=size[i];
			low[s]=min(low[s],low[i]);
            if(dfn[s]<=low[i]){
                son++;
                if(root!=0||son>1){
                    cutpoint[s]=true;
					ans[s]+=(long long)size[i]*res; //积累s[i]*res
                    res+=size[i];
                }
            }
        }
        else{
			low[s]=min(low[s],dfn[i]);
		}
    }
    ans[s]+=(long long)((n-res-1)*res); //积累特判
    size[s]++;
}
int main(){
	long long m;
	cin>>n>>m;
	for(long long i=1;i<=m;++i){
		long long x,y;
		cin>>x>>y;
		e[x].push_back(y);
		e[y].push_back(x);
	}
	for(long long i=1;i<=n;++i){
		if(!dfn[i]){
			tarjan(i,0);
		}
	}
	for(long long i=1;i<=n;++i){
		cout<<2*(ans[i]+n-1)<<endl; //加上刚才没加的
	}
}
posted @   HaneDaniko  阅读(18)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示