P3366 【模板】最小生成树
一道模板题,prim算法就可以过,krustal算法也是可以的嗷
prim算法
#include <bits/stdc++.h>
using namespace std;
const int N = 5010;
const int INF = 0x3f3f3f3f;
int n, m;
int g[N][N];
int dist[N];
bool st[N];
int prim()
{
memset(dist, 0x3f, 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;
}
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]);
st[t]=true;
}
return res;
}
int main()
{
cin >> n >> m;
memset(g, 0x3f, sizeof(g));
for (int i = 0; i < m; i++)
{
int a, b, w;
cin >> a >> b >> w;
g[a][b] = g[b][a] = min(g[a][b], w);
}
int t = prim();
if (t == INF) cout << "orz" << endl;
else cout << t << endl;
return 0;
}
krustal算法
#include <bits/stdc++.h>
using namespace std;
const int N = 200010;
int n, m;
int p[N]; //用并查集,维护边的权重,降低时间复杂度
struct Edge
{
int a, b, w;
bool operator<(const Edge &W) const
{
return w < W.w;
}
} edges[N];
int find(int x) //并查集中找祖宗节点+优化的函数
{
if (p[x] != x)
p[x] = find(p[x]);
return p[x];
}
int main()
{
cin >> n >> m;
for (int i = 0; i < m; i++)
{
int a, b, w;
cin >> a >> b >> w;
edges[i] = {a, b, w};
}
sort(edges, edges + m);
for (int i = 1; i <= n; i++) //初始化并查集
p[i] = i;
int res = 0; //存储最小生成树中所有边的权重之和
int cnt = 0; //存储当前一共加了多少条边
for (int i = 0; i < m; i++)
{
int a = edges[i].a, b = edges[i].b, w = edges[i].w;
a = find(a), b = find(b); //并查集中的算法
if (a != b)
{
p[a] = b;
res += w;
cnt++;
}
}
if (cnt < n - 1) //表示存在不连通的点,即不存在最小生成树
cout << "impossible" << endl;
else //输出答案
cout << res << endl;
return 0;
}
口袋的天空
krustal算法的应用
要用n个节点连接出k个连通块,就一定需要n-k条边
因此只需要在krustal算法枚举了n-k条边之后跳出循环即可找到答案
如果m<n-k则一定无法找到k个连通块,也就没有答案
#include <bits/stdc++.h>
using namespace std;
const int N = 200010;
int n, m, k;
int p[N]; //用并查集,维护边的权重,降低时间复杂度
struct Edge
{
int a, b, w;
bool operator<(const Edge &W) const
{
return w < W.w;
}
} edges[N];
int find(int x) //并查集中找祖宗节点+优化的函数
{
if (p[x] != x)
p[x] = find(p[x]);
return p[x];
}
int main()
{
cin >> n >> m >> k;
for (int i = 0; i < m; i++)
{
int a, b, w;
cin >> a >> b >> w;
edges[i] = {a, b, w};
}
sort(edges, edges + m);
for (int i = 1; i <= n; i++) //初始化并查集
p[i] = i;
int res = 0; //存储最小生成树中所有边的权重之和
int cnt = 0; //存储当前一共加了多少条边
for (int i = 0; i < m; i++)
{
int a = edges[i].a, b = edges[i].b, w = edges[i].w;
a = find(a), b = find(b); //并查集中的算法
if (a != b)
{
p[a] = b;
res += w;
cnt++;
}
if(cnt==n-k)
{
cout<<res<<endl;
return 0;
}
}
cout<<"No Answer"<<endl;
return 0;
}
拆地毯
超水一道题嗷,只需要把krustal算法模板中排序的顺序变一下就可
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
struct Edge
{
int a, b;
int w;
} edges[N];
int n, m, k;
int p[N];
int cmp(const Edge &a, const Edge &b)
{
return a.w > b.w;
}
int find(int x)
{
if (p[x] != x)
p[x] = find(p[x]);
return p[x];
}
int main()
{
cin >> n >> m >> k;
for (int i = 0; i < m; i++)
cin >> edges[i].a >> edges[i].b >> edges[i].w;
for (int i = 1; i <= n; i++)
p[i] = i;
sort(edges, edges + m, cmp);
int res = 0;
int cnt = 0;
for (int i = 0; i < m; i++)
{
int a = find(edges[i].a), b = find(edges[i].b);
if(a!=b)
{
p[a]=b;
res=res+edges[i].w;
cnt++;
}
if(cnt==k) break;
}
cout<<res<<endl;
return 0;
}
[USACO07DEC]Building Roads S
这道题用krustal算法思路还是比较清淅的
1、首先读入所有的点的坐标,存在p数组中
2、初始化所有可能存在的边的信息存在edges数组中
3、读入所有已经存在的边的,并将这些边的距离默认为0,存在edges数组中
4、对结构体按边权升序排列
5、进行krustal算法,当加入n-1条边的时候跳出循环,此时即可得到边权的最小值
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6+10; //注意这里的数据范围
int n, m;
struct point
{
double x, y;
} p[N];
struct Edge
{
int a, b;
double w;
} edges[N];
int cnt=1;
int b[N];
int find(int x)
{
if (b[x] != x)
b[x] = find(b[x]);
return b[x];
}
double length(int i, int j)
{
double res;
res = sqrt((p[i].x - p[j].x) * (p[i].x - p[j].x) + (p[i].y - p[j].y) * (p[i].y - p[j].y));
return res;
}
int cmp(const Edge &a, const Edge &b)
{
return a.w < b.w;
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> p[i].x >> p[i].y;
for (int i = 1; i <= n; i++)
{
for (int j = i + 1; j <= n; j++)
{
double w = length(i, j);
edges[cnt++] = {i, j, w};
}
}
for (int i = 1; i <= n; i++)
b[i] = i;
for (int i = 0; i < m; i++)
{
int a, b;
cin >> a >> b;
edges[cnt++] = {a, b, 0.0};
}
sort(edges + 1, edges + cnt, cmp);
int num=0;
double res=0;
for(int i=1;i<cnt;i++)
{
int x=find(edges[i].a),y=find(edges[i].b);
if(x!=y)
{
b[x]=y;
num++;
res=res+edges[i].w;
}
if(num==n-1) break;
}
printf("%.2lf\n",res);
return 0;
}
买礼物
这道题输入的矩阵不能直接用作邻接矩阵
因为 没有优惠的点之间 和 优惠方案的花费大于单独购买时 需要进行特殊处理
没有优惠的点之间:默认花费为单独购买的价格
优惠方案的花费大于单独购买时:优惠方案就不应该被选择,因此初始化成单独购买的价格
之后我们就只需要用最小生成树的算法来求解即可了
这里给出的题解是循环b次的pirm算法,只需要购买b个礼物因此循环b次即可
#include <bits/stdc++.h>
using namespace std;
const int N = 510;
typedef long long ll;
int a, b;
int g[N][N];
int dist[N];
bool st[N];
ll pirm()
{
memset(dist,0x3f,sizeof(dist));
ll res = 0;
for (int i = 0; i < b; i++)
{
int t = -1;
for (int j = 1; j <= b; j++) //找到未在集合中的点距离当前集合最近的点
{
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
}
if (i) //如果不是第一次的话,将距离叠加,只有一个点没有最小生成树的概念
res += dist[t];
for (int j = 1; j <= b; j++) //用这个点的距离更新这个点到集合的距离
dist[j] = min(dist[j], g[t][j]);
st[t] = true;
}
return res+a;
}
int main()
{
cin >> a >> b;
for (int i = 1; i <= b; i++)
{
for (int j = 1; j <= b; j++)
{
int w;
cin>>w;
if(w==0||w>a) g[i][j]=a;
else g[i][j]=w;
}
}
ll res=pirm();
cout<<res<<endl;
return 0;
}
营救
一看这道题的题目,求最大值最小,推测应该使用二分法
但是细想这道题根本没必要用二分法啊
因为在krustal算法中有对边权进行排序的操作,如果我们按照升序排序,那么后搜索到的边权一定大,所以在这里不在需要使用二分法
只需要注意在这里当s和t同属一个集合的时候应该跳出循环,此时搜索到的这条边的权重就是所有边权最大值最小的结果
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m,s,t;
struct Edge
{
int a,b;
int w;
}edges[N];
int p[N];
int cmp(const Edge &a,const Edge &b)
{
return a.w<b.w;
}
int find(int x)
{
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
int main()
{
cin>>n>>m>>s>>t;
for(int i=0;i<m;i++)
cin>>edges[i].a>>edges[i].b>>edges[i].w;
sort(edges,edges+m,cmp);
for(int i=0;i<n;i++) p[i]=i;
int res=0;
for(int i=0;i<m;i++)
{
int a=find(edges[i].a),b=find(edges[i].b);
if(a!=b)
{
p[a]=b;
res=edges[i].w;
}
if(find(s)==find(t)) break;
}
cout<<res<<endl;
return 0;
}
无线通讯网
这道题和krustal算法模板中唯一的区别就是退出循环的条件
这里只需要建立s-p条边即可,剩下的站点之间可以通过卫星电话来进行通话
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6;
int s, p;
double x[N], y[N];
int cnt=0;
struct Edge
{
int a,b;
double w;
}edges[N];
int b[N];
double length(int i, int j)
{
double res;
res = sqrt((x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]));
return res;
}
int find(int x)
{
if(b[x]!=x) b[x]=find(b[x]);
return b[x];
}
int cmp(const Edge &a, const Edge &b)
{
return a.w < b.w;
}
int main()
{
cin >> s >> p;
for (int i = 0; i < p; i++)
cin >> x[i] >> y[i];
for (int i = 0; i < p; i++)
{
for (int j = i + 1; j < p; j++)
{
double w = length(i, j);
edges[cnt++] = {i, j, w};
}
}
sort(edges,edges+cnt,cmp);
for(int i=0;i<=p;i++) b[i]=i;
int num=0;
double res=0;
for(int i=0;i<cnt;i++)
{
int x=find(edges[i].a),y=find(edges[i].b);
if(x!=y)
{
b[x]=y;
num++;
res=edges[i].w;
}
if(num==p-s) break;
}
printf("%.2lf\n",res);
return 0;
}
[JSOI2010]部落划分
整体思路与上一题类似嗷
初始将每一个点看作是一个部落,每在部落之间连一条边即可表示部落数量-1,当部落数量是我们要划分的数量时,下一条在部落之间连的线就是部落之间距离的最小值
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6;
int n, k;
double x[N], y[N];
int cnt = 0;
struct Edge
{
int a, b;
double w;
} edges[N];
int b[N];
double length(int i, int j)
{
double res;
res = sqrt((x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]));
return res;
}
int find(int x)
{
if (b[x] != x)
b[x] = find(b[x]);
return b[x];
}
int cmp(const Edge &a, const Edge &b)
{
return a.w < b.w;
}
int main()
{
cin >> n >> k;
for (int i = 0; i < n; i++)
cin >> x[i] >> y[i];
for (int i = 0; i < n; i++)
{
for (int j = i + 1; j < n; j++)
{
double w = length(i, j);
edges[cnt++] = {i, j, w};
}
}
sort(edges, edges + cnt, cmp);
for (int i = 0; i <= n; i++)
b[i] = i;
int num = n;
for (int i = 0; i < cnt; i++)
{
int x = find(edges[i].a), y = find(edges[i].b);
if (x != y)
{
b[x] = y;
num--;
}
if (num == k)
{
for (int j = i + 1; j < cnt; j++)
{
int x0 = find(edges[j].a), y0 = find(edges[j].b);
if (x0 != y0)
{
printf("%.2lf\n", edges[j].w);
break;
}
}
break;
}
}
return 0;
}