专栏文章

题解:P9424 [蓝桥杯 2023 国 B] 删边问题

P9424题解参与者 2已保存评论 1

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
1 条
当前快照
1 份
快照标识符
@min11e9i
此快照首次捕获于
2025/12/01 18:49
3 个月前
此快照最后确认于
2025/12/01 18:49
3 个月前
查看原文

题意

给定一个无向图 MM,删除一条边后满足剩余图恰好有 22强连通分量。
输出合法方案中两强连通分量点权差最小值,无合法方案则输出 1-1

思路

看到联通分量和删边,自然而然地想到桥(割边)
我们对原图中连通分量个数 sccscc 进行讨论。
  1. scc>2scc>2
    易得此时无论删除哪一条边 sccscc 都不可能等于 22,直接输出 1-1 即可。
  2. scc=2scc=2
    此时已经有两个连通分量,若要使连通分量个数不变,我们需要删除一条非桥边
    简单证明一下:如果我们删除的边为桥,那么其所在联通分量便会分裂为多个联通分量。此时 scc>2scc>2,不符合题意。
    若存在非桥边,输出答案即为两个初始连通分量点权和之差的绝对值。
    不存在则输出 1-1
  3. scc=1scc=1
    需要删除桥。
    那么该如何计算删除后两连通分量之差的最小值呢?
    可以将图搞成 dfs 生成树,在 Tarjan 求桥的过程中我们可以计算出每个节点的子树点权之和。
    设总点权为 sumsum,存在边 (u,v)(u,v) 为桥,ava_v 表示 vv 的子树和。易得另一部分点权和即为 sumavsum-a_v,两部分之差 ans=sum2avans=\left\vert sum-2a_v\right\vert
    由此,在这一情况中,若有桥则输出 ansans;无桥则输出 1-1

一些细节

十年 OI 一场空,不开 long long 见祖宗。

Code

CPP
#include<bits/stdc++.h>
#define int long long
#define JiuKu champion 
using namespace std;
const int N=200005;
int n,m,w[N],head[N],tmp[N],f[N],flag,cnt,sum,ans=1e18,scc[N],dfn[N],low[N];
//tmp[i]统计i的子树点权和。f[i]第i个连通分量是否有非桥边。flag整个图中是否有桥。 
//sum总点权。scc[0]统计scc总数。scc[i]计算第i个scc点权和 。 
struct edge{
	int to,next;
}e[2*N];
void add(int u,int v){
	e[++cnt].to=v;
	e[cnt].next=head[u];
	head[u]=cnt;
	return;
}
void Tarjan(int u,int fr){
	dfn[u]=low[u]=++cnt;
	scc[scc[0]]+=w[u],tmp[u]=w[u];
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].to;
		if(!dfn[v]){
			Tarjan(v,u);
			low[u]=min(low[u],low[v]);
			tmp[u]+=tmp[v];//累加子树点权。 
			if(dfn[u]<low[v]){
				flag=1;
				ans=min(ans,abs(sum-2*tmp[v])); 
			}//找到桥,更新若仅一个scc时答案。
		}else if(v!=fr){
			low[u]=min(low[u],dfn[v]);
			f[scc[0]]=1; //存在非桥边 。
		}
	}
	return;
}
signed main(){
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%lld",&w[i]);
		sum+=w[i];
	}
	for(int i=1;i<=m;i++){
		int u,v;
		scanf("%lld%lld",&u,&v);
		add(u,v),add(v,u);
	}
	cnt=0;
	for(int i=1;i<=n;i++){
		if(!dfn[i]){
			scc[0]++;
			Tarjan(i,0);
		}
	}
	if(scc[0]>=3){//scc多于两个,无解。 
		printf("-1");
	}else if(scc[0]==2){
		if(f[1] || f[2]){//scc=2且存在非桥边。 
			printf("%lld",abs(scc[2]-scc[1]));
		}else{
			printf("-1");
		}
	}else{
		if(!flag){//仅1个scc且不存在桥,无解。 
			printf("-1");
		}else{
			printf("%lld",ans);
		}
	}
	return 0;
}

若有错误请指出。

评论

1 条评论,欢迎与作者交流。

正在加载评论...