最新文章
这里会显示最新的几篇文章摘要。
记录生活,分享知识,与你一起成长。
这里会显示最新的几篇文章摘要。
在一个社交网络服务(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解释:
三个新的友谊可以如下产生:
不会有四个或更多的新友谊产生。
我们可以证明:对每个连通块,当它不是团(完全图)时,总能找到满足条件的三个点进行操作,并且不断执行操作直到这个连通块变成一个团为止。对于一个有 (k) 个节点的连通块,其完全图边数为
\(
\frac{k(k-1)}{2}
\)
假设该连通块初始时有 (m) 条边,则最多可以“补全”
\(
\frac{k(k-1)}{2} - m
\)
条边,也就是操作的最大次数。由于不同连通块之间没有路径,操作只能在各自连通块内进行,所以整个图中可以执行的最大操作次数为所有连通块中“缺失边数”的和,即
\(
\text{答案} = \sum_{\text{连通块}} \left(\frac{k(k-1)}{2} - m\right)
\)
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;
}