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;
}