标签:中间 距离 clu puts 注意 size 生成森林 return ++
对于图 $ G = (V,E) $, 有 \(n\) 个点, \(m\) 条边, 由 \(V\) 中所有 \(n\) 个点和 \(E\) 中 \(n-1\) 条边构成的一个连通子图(即一棵树),称为 \(G\) 的一个生成树, 边权值最小的为最小生成树.
一般用于稠密图:
#include <iostream>
#include <cstring>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 510;
int n,m;
int g[N][N]; //稠密图
int dist[N]; //表示某个结点到当前集合的最小距离(与dijkstra不同)
int st[N]; //是否在集合内
int prim()
{
memset(dist, INF, sizeof dist);
int res = 0;
for (int i = 0; i < n; i ++ ){
int t = -1;
for (int j = 1; j <= n; j ++ ) //寻找不在集合内,且到集合距离最小的结点
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
st[t] = 1; //进入集合
if (i && dist[t] == INF) return INF; //不存在生成树
if (i) res += dist[t];
for (int j = 1; j <= n; j ++ ) //更新其它结点
dist[j] = min(dist[j], g[t][j]);
}
return res;
}
int main()
{
cin >> n >> m;
memset(g, INF, sizeof g);
for (int i = 1; i <= m; i ++ ){
int a, b, v;
cin >> a >> b >> v;
g[a][b] = g[b][a] = min(g[a][b], v); //无向图
}
int t = prim();
if (t == INF)
puts("impossible");
else
cout << t << endl;
return 0;
}
一般用于稀疏图
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
const int M = 2 * N;
int n,m;
int f[N]; //并查集的操作
struct Edge{
int a, b;
int v;
}edge[M];
bool comp(Edge x, Edge y) //自定义比较
{
return x.v < y.v;
}
int find(int x) //寻找祖宗结点
{
if (f[x] != x) return f[x] = find(f[x]);
return f[x];
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= m; i ++ ){
int a, b, v;
cin >> a >> b >> v;
edge[i] = {a, b, v};
}
sort(edge + 1, edge + m + 1, comp);
for (int i = 1; i <= n; i ++ ) //并查集的初始化
f[i] = i;
int res = 0; //记录距离之和
int cnt = 0; //存储结点数量
for (int i = 1; i <= m; i ++ ){ //枚举每条边
int a = edge[i].a;
int b = edge[i].b;
int v = edge[i].v;
int fa = find(a);
int fb = find(b);
if (fa != fb){ //合并集合
f[fa] = fb;
res += v;
cnt ++;
}
}
if (cnt < n - 1)
puts("impossible");
else
cout << res << endl;
return 0;
}
最小生成树的模板题, 输入矩阵形式, 采用prim算法
#include <iostream>
#include <cstring>
using namespace std;
const int N = 110;
int g[N][N];
int n;
int dist[N];
int st[N];
int prim()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
int res = 0;
for (int i = 1; i <= n; i ++ ){
int t = -1;
for (int j = 1; j <= n; j ++ )
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
st[t] = 1;
res += dist[t];
for (int j = 1; j <= n; j ++ )
dist[j] = min(dist[j], g[t][j]);
}
return res;
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
cin >> g[i][j];
cout << prim() << endl;
return 0;
}
kruakal算法求最小生成树, 注意每次当两个点需要删边时, 将结果加上权值.
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110, M = 220;
struct Edge{
int a, b, v;
bool operator < (const Edge &t)const
{
return v < t.v;
}
}edge[M];
int fa[N];
int n ,m;
int find(int x)
{
if (fa[x] != x)
fa[x] = find(fa[x]);
else
return x;
}
int main()
{
cin >> n >> m;
int res = 0;
for (int i = 1; i <= n; i ++ )
fa[i] = i;
for (int i = 1; i <= m; i ++ ){
int a, b, v;
cin >> a >> b >> v;
edge[i] = {a, b, v};
}
sort(edge + 1, edge + m + 1);
for (int i = 1; i <= m; i ++ )
cout << edge[i].v << endl;
for (int i = 1; i <= m; i ++ ){
int a = edge[i].a;
int b = edge[i].b;
int v = edge[i].v;
int aa = find(a);
int bb = find(b);
if (aa != bb) fa[aa] = bb;
else
res += v;
}
cout << res << endl;
return 0;
}
同样是模板题, 注意理解题意, 要求被改造的道路能够连通整个图(被选入生成树里的点)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 310, M = 8010;
struct Edge{
int a, b, v;
bool operator < (const Edge &t)const
{
return v < t.v;
}
}edge[M];
int n,m;
int fa[N];
int sum;
int find(int x)
{
if (fa[x] != x)
fa[x] = find(fa[x]);
else
return x;
}
int main()
{
cin >> n >> m;
int res = 0;
for (int i = 1; i <= m; i ++ ){
int a, b, v;
cin >> a >> b >> v;
edge[i] = {a, b, v};
}
sort(edge + 1, edge + m + 1);
for (int i = 1; i <= n; i ++ )
fa[i] = i;
for (int i = 1; i <= m; i ++ ){
int a = edge[i].a;
int b = edge[i].b;
int v = edge[i].v;
int aa = find(a);
int bb = find(b);
//cout << aa << ‘ ‘ << bb << endl;
if (aa == bb)
continue;
else{
sum ++;
fa[aa] = bb;
res = v;
}
}
cout << sum << ‘ ‘ << res << endl;
return 0;
}
图中含有必选边和非必选边, 对于必选边我们直接加入到生成树中, 然后再根据kruskal算法加入非必选边
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2010, M = 10010;
struct Edge{
int op, a, b, v;
bool operator < (const Edge &t)const
{
if (op == t.op)
return v < t.v;
return op < t.op;
}
}edge[M];
int fa[N];
int n, m;
int find(int x)
{
if (fa[x] != x)
fa[x] = find(fa[x]);
else
return x;
}
int main()
{
cin >> n >> m;
int res = 0;
for (int i = 1; i <= n; i ++ )
fa[i] = i;
for (int i = 1; i <= m; i ++ ){
int op, a, b, v;
cin >> op >> a >> b >> v;
edge[i] = {op, a, b, v};
}
sort(edge + 1, edge + m + 1);
for (int i = 1; i <= m; i ++ ){
int op = edge[i].op;
int aa = find(edge[i].a);
int bb = find(edge[i].b);
int v = edge[i].v;
if (op == 1){
res += v;
if (aa != bb)
fa[aa] = bb;
}else{
if (aa == bb)
continue;
else{
res += v;
fa[aa] = bb;
}
}
}
cout << res << endl;
return 0;
}
原图中有 \(m * n\) 个点, 其中横向点之间、纵向点之间都有边, 根据kruskal算法,先加入纵向边(权值小),再加入横向边(权值大).
我们可以直接在加边时进行排序,避免sort().
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2 * 1e6 + 10, M = 2 * 1e6 + 10;
int g[1010][1010];
struct Edge{
int a, b, v;
bool operator < (const Edge &t)const
{
return v < t.v;
}
}edge[M];
int fa[N];
int n, m;
int find(int x)
{
if (fa[x] != x)
fa[x] = find(fa[x]);
return fa[x];
}
int main()
{
cin >> n >> m;
for (int i = 1, t = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
g[i][j] = t ++;
for (int i = 1; i <= n * m; i ++ )
fa[i] = i;
int cnt = 0;
for (int i = 1; i <= m; i ++ )
for (int j = 1; j < n; j ++ ){
int a = g[j][i], b = g[j + 1][i];
int v = 1;
//cout << a << ‘ ‘ << b << endl;
edge[cnt ++] = {a, b, v};
}
for (int i = 1; i <= n; i ++ )
for (int j = 1; j < m; j ++ ){
int a = g[i][j], b = g[i][j + 1];
int v = 2;
//cout << a << ‘ ‘ << b << endl;
edge[cnt ++] = {a,b,v};
}
int x1, y1, x2, y2;
while (~scanf("%d%d%d%d",&x1, &y1, &x2, &y2)){
int a = g[x1][y1];
int b = g[x2][y2];
int aa = find(a);
int bb = find(b);
fa[aa] = bb;
}
int res = 0;
for (int i = 0; i < cnt; i ++ ){
int aa = find(edge[i].a);
int bb = find(edge[i].b);
int v = edge[i].v;
if (aa == bb)
continue;
else{
res += v;
fa[aa] = bb;
}
}
cout << res << endl;
return 0;
}
图论中常用的假设虚拟原点问题, 假设一个虚拟原点, 到点 \(i\) 的边的权值为在该点建立电站\(v_{i}\), 求一个包含所有点的最小生成树.
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 310;
int g[N][N];
int v[N];
int n;
int dist[N];
int st[N];
int prim()
{
memset(dist, 0x3f, sizeof dist);
dist[n + 1] = 0;
int res = 0;
for (int i = 0; i <= n; i ++ ){
int t = -1;
for (int j = 1; j <= n + 1; j ++ )
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
st[t] = 1;
if (i)
res += dist[t];
for (int j = 1; j <= n + 1; j ++ )
dist[j] = min(dist[j], g[t][j]);
}
return res;
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i ++ )
cin >> v[i];
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
cin >> g[i][j];
for (int i = 1; i < n + 1; i ++ )
g[n + 1][i] = g[i][n + 1] = v[i];
g[n + 1][n + 1] = 0;
cout << prim() << endl;
return 0;
}
图中有 \(n\) 个点, 有 \(n*n\) 条路径. 假设起始状态都不连通, 即有 \(n\) 个连通块, 根据kruakal算法,每次找到一个边权值最小的点, 把该点加入到生成树中, 直到剩余 \(k\) 个连通块, 我们可以直接用卫星连通.
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
typedef pair<int,int> PII;
const int N = 510, M = N * N;
int n, k;
struct Edge{
int a, b;
double v;
bool operator < (const Edge &t)const
{
return v < t.v;
}
}edge[M];
PII p[N];
int fa[N];
int find(int x)
{
if (fa[x] != x)
fa[x] = find(fa[x]);
return fa[x];
}
double get_len(PII a, PII b)
{
double dx = a.first - b.first;
double dy = a.second - b.second;
return sqrt(dx * dx + dy * dy);
}
int main()
{
cin >> n >> k;
for (int i = 1; i <= n; i ++ ){
cin >> p[i].first >> p[i].second;
fa[i] = i;
}
int cnt = 0;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= i; j ++ ){
double v = get_len(p[i], p[j]);
edge[cnt ++] = {i,j,v};
}
sort(edge, edge + cnt);
double ans = 0;
int block = n;
for (int i = 0; i < cnt; i ++ ){
int aa = find(edge[i].a);
int bb = find(edge[i].b);
double v = edge[i].v;
if (block == k)
break;
ans = v;
if (aa != bb){
block --;
fa[aa] = bb;
}
}
printf("%.2f", ans);
return 0;
}
完全图 : 完全图是一个简单的无向图,其中每对不同的顶点之间都恰连有一条边相连.
题目要求: 满足图的唯一最小生成树仍然是原树, 同时, 要求所加权值之和最小.
每次将两个连通块通过已有边连接时, 需要将两个连通块中的点都连一条边, 数量为 \(size[a] * size[b] - 1\) ,同时满足所加边权均为 \(v + 1\) (同时满足权值最小和生成树唯一).
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 6010;
struct Edge{
int a, b, v;
bool operator < (const Edge &t)const
{
return v < t.v;
}
}e[N];
int fa[N];
int s[N];
int n;
int find(int x)
{
if (x != fa[x])
fa[x] = find(fa[x]);
return fa[x];
}
int main()
{
int t;
cin >> t;
while (t -- ){
cin >> n;
for (int i = 1; i < n; i ++ ){
int a, b, v;
cin >> a >> b >> v;
e[i] = {a, b, v};
}
sort(e + 1, e + n);
int res = 0;
for (int i = 1; i <= n; i ++ ){
fa[i] = i;
s[i] = 1;
}
for (int i = 1; i < n; i ++ ){
int a = find(e[i].a);
int b = find(e[i].b);
int v = e[i].v;
if (a != b){
res += (s[a] * s[b] - 1) * (v + 1);
s[b] += s[a];
fa[a] = b;
}
}
cout << res << endl;
}
return 0;
}
标签:中间 距离 clu puts 注意 size 生成森林 return ++
原文地址:https://www.cnblogs.com/lhqwd/p/14546939.html