Hongcow Builds A Nation 题解

Hongcow Builds A Nation 题解

更好的阅读体验?

洛谷

Codeforces

题目描述

给定一张 n n n 个点, m m m 条边的无向图,有 k k k 个点是特殊点。

每个连通块中都得保证无重边、无自环,且最多只有一个特殊点。

求最多还能加多少条边,满足以上条件。

思路简述

首先考虑以下有 n n n 个点的完全图共有多少条边:

第一个点可以和剩下的 n − 1 n-1 n1 个点匹配,第二个点可以和 n − 2 n-2 n2 个点匹配……
( n − 1 ) + ( n − 2 ) + ⋯ + 2 + 1 = n ⋅ ( n − 1 ) 2 = n 2 − n 2 (n-1)+(n-2)+\cdots +2+1 \\ =\frac{n \cdot(n-1)}{2} \\ =\frac{n^2-n}{2} (n1)+(n2)++2+1=2n(n1)=2n2n
因为题目中提到了连通块这一关键词,立马联想到用并查集来实现。

假设连通块 x x x 与连通块 y y y 可以合并,那么显然需要满足以下条件:

  • 连通块 x x x 与连通块 y y y 合起来最多只有一个特殊点。

当我们将所有内部有特殊点的连通块都合并完后,考虑无特殊点的连通块。

可以开个变量 num 统计所有无特殊点的连通块的成员数量和。

很显然,将所有无特殊点的连通块全部与连通块内节点数量最多的连通块合并起来最优。

那么我们就可以开个变量表示连通块内节点数量的最大值。最后全部内部无特殊点的连通块合并入最大值即可。

答案怎么统计呢?

考虑枚举所有连通块。

如果当前连通块内部没有特殊点,直接加入 num。继续访问下一个。

若当前连通块不是目前最大连通块,则用公式(上文提及)加入答案。

如果当前连通块大于目前最大连通块:

设当前连通块成员数量为 x x x,最大连通块成员数量为 m a x n maxn maxn

1、用目前最大连通块合并入当前连通块,答案加上 m a x n 2 − m a x n 2 \frac{maxn^2-maxn}{2} 2maxn2maxn

2、更新。 m a x n maxn maxn 赋值为 x x x

3、注意顺序不能反。

Code

//wrote by Atserckcn
#include<bits/stdc++.h>
using namespace std;
#define ljl long long
const ljl N=1005,M=1e5+5;
ljl n,m,k,cnt_e,u,v,head[N],pl[N],fa[N],cnt_node[N],maxn,ans,num;
bool pol[N],vis[N];
//突然发现建边貌似并没有用,可以忽略建边的一系列操作。
struct E{
	ljl to,pre;
}e[M<<1];
void add(ljl from,ljl to)
{
	e[++cnt_e].to=to;
	e[cnt_e].pre=head[from];
	head[from]=cnt_e;
	return;
}
//并查集模板--------------------
ljl findfa(ljl x)
{
	return fa[x]==x?x:fa[x]=findfa(fa[x]);
}
void Union(ljl x,ljl y)
{
	fa[findfa(x)]=findfa(y);
	return;
}
//并查集模板--------------------
ljl getnum(ljl x)//公式
{
	return x*(x-1)/2;
}
int main(){
    //浅浅关个同步流
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n>>m>>k;
	for(ljl i=1;i<=n;++i)
		fa[i]=i;//并查集的初始化
	for(ljl i=1;i<=k;++i)
	{
		cin>>pl[i];
		pol[pl[i]]=true;//标记此乃特殊点
	}
	for(ljl i=1;i<=m;++i)
	{
		cin>>u>>v;
		add(u,v);add(v,u);
		Union(u,v);//并查集的合并
	}
	for(ljl i=1;i<=n;++i)
	{
		++cnt_node[findfa(i)];//统计每个连通块内成员个数
		pol[findfa(i)] |= pol[i];//若当前节点是特殊点,那么这个连通块肯定也是
	}
	for(ljl i=1;i<=n;++i)
	{
		ljl fx=findfa(i);
		if(vis[fx]) continue;
		vis[fx]=1;
        //开始实施答案统计
		if(pol[fx])
		{
			if(cnt_node[fx]<=maxn)
				ans+=getnum(cnt_node[fx]);
			else
			{
				ans+=getnum(maxn);
				maxn=cnt_node[fx];
			}
		}
		else
			num+=cnt_node[fx];
	}
	ans+=getnum(maxn+num);
	cout<<ans-m<<'\n';
	return 0;
}

AC 记录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值