https://codeforces.com/contest/1253/problem/D
题意:
有一个n点m边的无向图
调和图的定义: 假如图中l,r能相互抵达,那么l和l+1,l+2,……, r - 1 也能相互抵达
求一个图变成调和图最少需要加多少条边
输入:
第一行n,m (n<1e5,m<1e5)
接下来m行两个数i,j,表示有i到j的无向边
看到这道题的数据量我就觉得不对劲,然后就想到了并查集。。。。
因为l和l+1,l+2,……,r 能够相互到达,则说明 l,l+1,l+2 …… r 这些点都能够相互到达。
那么将这些能相互到达的用并查集维护起来,缩点,每个并查集的根节点保存该并查集树里的节点的最大值和最小值
然后对于每棵并查集树,在原序列一个一个检查其最小值到最大值中间是否都属于这棵并查集树,假如不属于,则合并两个并查集,然后统计合并次数。。。
代码:
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 2e5 + 7;
int fat[maxn], minn[maxn], maxx[maxn], siz[maxn];
int n, m;
vector<int> boss;
int trace(int x) {
return fat[x] == x ? x : fat[x] = trace(fat[x]);
}
void combine(int a, int b) {
int f_a = trace(a), f_b = trace(b);
if (f_a != f_b) {
fat[f_a] = f_b;
siz[f_b] += siz[f_a];
minn[f_b] = min(minn[f_a], minn[f_b]);
maxx[f_b] = max(maxx[f_a], maxx[f_b]);
}
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++)
fat[i] = minn[i] = maxx[i] = i, siz[i] = 1;
int in1, in2;
//缩点
for (int i = 1; i <= m; i++) {
scanf("%d %d", &in1, &in2);
combine(in1, in2);
}
int fi, ans = 0;
//寻找根节点
for (int i = 1; i <= n; i++) {
fi = trace(i);
if (fat[i] == i && siz[i] > 1)
boss.push_back(i);
}
//对于每个根节点检查其最小到最大值是否每个值都属于这棵树
for (int i = 0; i < boss.size(); i++) {
boss[i] = trace(boss[i]);
if (siz[boss[i]] == maxx[boss[i]] - minn[boss[i]] + 1) continue;
for (int l = minn[boss[i]]; l <= maxx[boss[i]]; l++) {
trace(l);
if (fat[l] != boss[i]) {
combine(l, boss[i]), ans++;
}
}
}
cout << ans << endl;
return 0;
}