icpc2023网络预选赛9.17第一场

D题


大致题意
给定一个无向图,要求添加最少数量的边,至少添加一条边,使得所有子图为完全图,求最少添加多少条边。

思路
首先判断有多少个子图,划分子图的条件为该点是否与其他点联通(假设一个点与一个子图中的任意点有边,那么该点属于这个子图)。然后遍历所有子图,判断该子图是否为完全图(子图中任意两点都有边连接),如不为完全图,则记录还需要加多少条边才能将这个子图变为完全图,累加求和。如果所有子图都为完全图,就选择两个点数最小的子图进行合并,变成一个完全图(至少添加一条边)。

使用map进行记录查找,超时。使用并查集计算,通过测试。

测试样例

样例输入

4 3
1 2
1 3
2 3

样例输出

3

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
const int MAX=1e6+5;
int point[MAX]={0},sum[MAX]={0},cnt[MAX]={0};//父节点 点数 边数 
bool flag[MAX]={false};
vector<int> ve;
ll ans=0,n,m,u,v,all=0;
//寻找父节点并压缩路径 
int find(int x){
	if(point[x]==0)return 0;
	if(point[x]==x)return x;
	point[x]=find(point[x]);
	return point[x];
}
int main(){
	//n个点m条边
    cin>>n>>m; 
    for(int i=1;i<=m;i++){
    	cin>>u>>v;
    	if(u>v){
    		int tmp=u;
    		u=v;
    		v=tmp;
		}
		//注意,判断集合是否相同之前,先进行路径压缩 
		//找父节点,父节点相同的在同一个集合
		int k=find(point[u]),f=find(point[v]); 
		if(k==0&&f==0){//新集合 
			point[u]=point[v]=u;
			flag[u]=true;
			sum[u]+=2;
			cnt[u]+=1;
		}
		else if(k==0){
			point[u]=point[f];
			sum[f]+=1;
			cnt[f]+=1;
		}
		else if(f==0){
			point[v]=point[k];
			sum[k]+=1;
			cnt[k]+=1;
		}
		else if(k!=f){//两集合进行合并 
			flag[f]=false;
			point[f]=point[k];
			sum[k]+=sum[f];
			cnt[k]+=cnt[f]+1;
		}
		else{//两点在同一集合,直接加边 
			cnt[k]+=1;
		}
	}
    for(int i=1;i<=n;i++){
    	if(flag[i]){
    		ve.push_back(sum[i]);
    		all+=sum[i];
    		if((sum[i]*(sum[i]-1))/2>cnt[i]){
    			ans+=(sum[i]*(sum[i]-1))/2-cnt[i];
			}
		}
	}
	//子图都是完全图,合并两个点数最小的子图 
	//从小到大排序,选择最小的两个集合,单点集合不在ve中存储,需单独计算 
	sort(ve.begin(),ve.end());
	if(!ans){
		if(all<=n-2){//选择两个单点集合连接成完全图 
			ans=1;//一条边 
		}
		else if(all==n-1){
			ans=ve[0];
		}
		else{
			ans=ve[0]*ve[1];
		}
	}
    cout<<ans<<endl;
	return 0;
}
posted @ 2023-09-17 19:59  skdtxdy  阅读(26)  评论(0编辑  收藏  举报