欢迎来到我的算法与代码笔记(建设中)

在这里记录学习之旅,分享编程心得,用二次元的活力激发灵感!

开始阅读

随笔标题一

简要摘要介绍,方便读者快速了解文章主要内容。

随笔标题二

简要摘要介绍,方便读者快速了解文章主要内容。

社交网络(并查集)

问题 C: 社交网络

题目描述

在一个社交网络服务(SNS)中,有N个用户,分别用从1到N的数字标记。

在这个SNS中,两个用户可以成为朋友。友谊是双向的;如果用户X是用户Y的朋友,那么用户Y总是用户X的朋友。

目前,在SNS上有M对朋友关系,第i对由用户Ai和Bi组成。

确定以下操作可以执行的最大次数:

操作:选择三个不同的用户 X 、Y和 Z ,使得 X 和 Y 是朋友,Y 和 Z 是朋友,但 X 和 Z不是朋友。让 X 和 Z 成为朋友。

输入

第一行包含两个正整数 N 和 M,表示有 N 个用户,M 对朋友关系

接下来 M 行,每行描述第 i 对朋友关系。

输出

一行一个整数,表示答案。

样例输入

【样例1】
4 3
1 2
2 3
1 4
【样例2】
3 0
【样例3】
10 8
1 2
2 3
3 4
4 5
6 7
7 8
8 9
9 10

样例输出

【样例1】3
【样例2】0
【样例3】12

提示

样例1解释:
三个新的友谊可以如下产生:

  1. 用户1与用户3成为朋友,用户3是他们的朋友(用户2)的朋友。
  2. 用户3与用户4成为朋友,用户4是他们的朋友(用户1)的朋友。
  3. 用户2与用户4成为朋友,用户4是他们的朋友(用户1)的朋友。

不会有四个或更多的新友谊产生。

  • 对于 40% 的数据,1 ≤ N ≤ 1000
  • 对于 100% 的数据,1 ≤ N ≤ 2*10, 0 ≤ M ≤ 2*10, 1 ≤ Ai < Bi ≤ N 数据保证没有自环。注意,重复的边算一条边。

分析

我们可以证明:对每个连通块,当它不是团(完全图)时,总能找到满足条件的三个点进行操作,并且不断执行操作直到这个连通块变成一个团为止。对于一个有 (k) 个节点的连通块,其完全图边数为
k(k1)2
假设该连通块初始时有 (m) 条边,则最多可以“补全”
k(k1)2m
条边,也就是操作的最大次数。由于不同连通块之间没有路径,操作只能在各自连通块内进行,所以整个图中可以执行的最大操作次数为所有连通块中“缺失边数”的和,即
答案=连通块(k(k1)2m)

代码思路就是在并查集的基础上,再维护两个数组,一个cnt[N]储存节点个数,一个e[N]储存边的个数,都储存在根节点位置,再维护一个领接表储存边,用来确保边不重复。

代码

#define int long long
const int N = 2e5 + 5;
int fa[N],e[N],cnt[N]; //分别为并查集根节点,边数,节点数
vector<int> ed[N]; //领接表存边
inline void init(int n) { //初始化
for (int i = 1;i <= n;++i) {
fa[i] = i;
cnt[i] = 1;
}
}
int find_fa(int x) { //查找,路径压缩
if (fa[x] != x) fa[x] = find_fa(fa[x]);
return fa[x];
}
inline void merge(int a,int b) { //合并集合
int t = find_fa(a),q = find_fa(b);
if (t == q) {e[t]++;return;} //注意,如果两元素在同一个集合,但是边是不重复的,那么边数加一
cnt[t] += cnt[q]; //把b集合的个数加到a集合上
e[t] += e[q]+1; //把b集合的边数加到a集合上
fa[q] = t; //合并
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
//
// freopen("E:/Code/C++/untitled1/input.txt","r",stdin);
// freopen("output.txt","w",stdout);
int n,m;
cin >> n >> m;
init(n);
for (int i = 0;i < m;++i) {
int a,b;
cin >> a >> b; //读入边
bool f = false; //标记
for (auto c : ed[a]) if (c == b) {f = true;break;} //检查是否是重边
if (f) continue; //重边跳过
ed[a].emplace_back(b); //记录边
ed[b].emplace_back(a);
merge(a,b);
}
int ans = 0;
for (int i = 1;i <= n;++i) {
if (i == fa[i]) { //检查有哪些连通块
ans += 1LL*cnt[i]*(cnt[i]-1)/2 - e[i]; //计算结果
}
}
cout << ans;
}
posted @   bakul  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示