让悲伤不再重来
C - Connect Cities
有N个城市(编号1到N)和M条双向道路(编号1到M)。道路 i 连接城市 A_i 和城市 B_i 。
Snuke 可以执行以下操作 0 次或更多次。
完成后,您应该能够通过沿着道路(可能多次)从任何城市到达任何城市。
要达到目标,最少要修建多少条道路?
输入从标准输入以下列格式给出。
打印出你的答案。
3 1
1 2
1
首先,有3个城市,城市 1 和城市 2 之间有一条道路。
例如,Snuke 可以通过在城市 1 和城市 3 之间修建一条道路来实现他的目标。修好路后
如果所有道路都是连通的,它将是0,不需要修路。
一般来说,如果有 K 个连通分量,则可以通过 K-1 条边连接整体。
相反,添加一条边不能将连接组件的数量减少两个以上。因此,如果有 K 个连通分量,则至少需要 K-1 条边。
综上所述,
我发现它是通过以下方式需要的。
Union-Find
说到组,Union-Find。Union-Find 允许对连接的组件进行有效分组。
具体来说,对于每条边 (u,v),
uf.merge(u, v);
最后,在计算组数时,请执行以下操作:
基于以上,可以如下实现。复杂度为 O((N+M)α(N))) 。
#include <bits/stdc++.h>
using namespace std;
struct UnionFind {
vector<int> par;
UnionFind(int n) : par(n, -1) { }
void init(int n) { par.assign(n, -1); }
int root(int x) {
if (par[x] < 0) return x;
else return par[x] = root(par[x]);
}
bool issame(int x, int y) {
return root(x) == root(y);
}
bool merge(int x, int y) {
x = root(x); y = root(y);
if (x == y) return false;
if (par[x] > par[y]) swap(x, y); // merge technique
par[x] += par[y];
par[y] = x;
return true;
}
int size(int x) {
return -par[root(x)];
}
};
int main() {
int N, M;
cin >> N >> M;
UnionFind uf(N);
for (int i = 0; i < M; ++i) {
int a, b; cin >> a >> b; --a, --b;
uf.merge(a, b);
}
int res = 0;
for (int i = 0; i < N; ++i) if (uf.root(i) == i) ++res;
cout << res - 1 << endl;
}
struct UnionFind {
int num;
vector<int> rs, ps;
UnionFind() {}
UnionFind(int n) : num(n), rs(n, 1), ps(n, 0) { iota(ps.begin(), ps.end(), 0); }
int find(int x) {
return (x == ps[x] ? x : ps[x] = find(ps[x]));
}
bool same(int x, int y) {
return find(x) == find(y);
}
void unite(int x, int y) {
x = find(x);
y = find(y);
if (x == y) return;
if (rs[x] < rs[y]) swap(x, y);
rs[x] += rs[y];
ps[y] = x;
num--;
}
int size(int x) {
return rs[find(x)];
}
int count() const {
return num;
}
};
//INSERT ABOVE HERE
signed main() {
cin.tie(0);
ios::sync_with_stdio(0);
int n, m;
cin >> n >> m;
UnionFind uf(n);
for (int i = 0; i < m; i++) {
int a, b;
cin >> a >> b;
a--;
b--;
uf.unite(a, b);
}
cout << uf.count() - 1 << newl;
return 0;
}
还有基于图搜索解决方案的解决方案。所说的DFS
因此,对于每个连通分量,在其中的一点 v 上调用递归函数 dfs(v) 将访问连通分量中包含 v 的所有顶点。
基于以上考虑,可以通过下面的实现来统计连接组件的数量。
int counter = 0;
vector<bool> seen(N, false);
for (int v = 0; v < N; ++v) {
if (seen[v]) continue;
dfs(v);
++counter;
}
换句话说,重复搜索,直到搜索完所有顶点。
每次调用 dfs(v) 时,都会调用“新连通分量中的顶点 v”。
因此,如果您在调用 dfs(v) 时增加 counter,则 counter 的值将是您想要的连接组件的数量。
#include <bits/stdc++.h>
using namespace std;
using Graph = vector<vector<int>>;
void dfs(const Graph &G, vector<bool> &seen, int v) {
seen[v] = true;
for (auto nv : G[v]) if (!seen[nv]) dfs(G, seen, nv);
}
int main() {
int N, M;
cin >> N >> M;
Graph G(N);
for (int i = 0; i < M; ++i) {
int a, b; cin >> a >> b; --a, --b;
G[a].push_back(b), G[b].push_back(a);
}
int res = 0;
vector<bool> seen(N, false);
for (int v = 0; v < N; ++v) {
if (seen[v]) continue;
dfs(G, seen, v);
++res;
}
cout << res - 1 << endl;
}
BFS也可以解决,思路和DFS完全一样。
#include <bits/stdc++.h>
using namespace std;
using Graph = vector<vector<int>>;
void bfs(const Graph &G, vector<int> &dist, int s) {
dist[s] = 0;
queue<int> que;
que.push(s);
while (!que.empty()) {
int v = que.front(); que.pop();
for (auto nv : G[v]) {
if (dist[nv] == -1) {
dist[nv] = dist[v] + 1;
que.push(nv);
}
}
}
}
int main() {
int N, M;
cin >> N >> M;
Graph G(N);
for (int i = 0; i < M; ++i) {
int a, b; cin >> a >> b; --a, --b;
G[a].push_back(b), G[b].push_back(a);
}
int res = 0;
vector<int> dist(N, -1);
for (int v = 0; v < N; ++v) {
if (dist[v] != -1) continue;
bfs(G, dist, v);
++res;
}
cout << res - 1 << endl;
}
问题可以重述如下。
有一个有 N 个顶点和 M 个边的无向图 G,其中第 i 条边连接第 A_i 顶点和第 B_i 顶点。连接 G 必须添加的最小边数是多少?
为了解决这个问题,我们关注连接组件的数量。例如,如果有 1 个连通分量,则答案为 0,因为条件已经满足。此外,如果有 2 个连通分量,则答案为 1,因为通过在从两个连通分量中选择一个的顶点之间添加一条边来满足条件。
这样思考,我们可以看到,通过从两个不同的连通分量中逐一选择顶点并添加连接两个顶点的边,可以将连通分量的数量减少一个。所以答案是(G 的连通分量数)-1。
有几种技术可以在图中找到连通分量的数量。
Union-Find是将每一组视为一棵树的数据结构,通过两个顶点所属的树的根是否相同来判断两个顶点是否在同一个组中。在 C++ 的情况下,可以使用 ACL dsu
轻松实现,可以通过包含 atcoder/all 或 atcoder/dsu 来使用。
実装例(C++)
#include <bits/stdc++.h>
#include <atcoder/all>
using namespace std;
using namespace atcoder;
int main(){
int n,m;
cin>>n>>m;
//n頂点のグラフgを用意する
dsu g(n);
for(int i=0;i<m;i++){
int a,b;
cin>>a>>b;
//入力の1がgでの0に対応
a--;b--;
//辺を追加する
g.merge(a,b);
}
//連結成分の数-1が答え
int ans=g.groups().size()-1;
cout<<ans<<endl;
}
END