【知识点复习】最小生成树
前言
没有前言
我的 终究还是倒了555.
参考链接:数据结构--最小生成树详解 以及 《算法进阶指南》
介绍
最小生成树,其实就是给定 个点 条边,从 条边中选出 条边使得边权和最小。
定理:任意一棵最小生成树一定包含无向图中权值最小的边。
可以用反证法证明,具体过程参见《算法进阶指南》
而利用最小生成树的 性质就可以生成最小生成树,下面介绍两种常用的最小生成树算法。
Kruskal 算法
核心思想:维护无向图的最小生成森林。
思路:先按边权排序,利用并查集判断连接一条边的两个端点是否联通,如果联通就跳下一边;不连通就作为树边连上,直到将无向图变为树。
代码:
struct edge {
int x, y, val;
};
bool cmp(node a, node b) {
return a.val < b.val;
}
int find(int x) {
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
void Keuskal() {
sort(edge + 1, edge + 1 + m, cmp);
for(int i = 1; i <= n; i ++) fa[i] = i;
for(int i = 1; i <= m; i ++) {
int tx = find(edge[i].x), ty = find(edge[i].y);
if(tx != ty) {
fa[tx] = ty;
sum += edge[i].val;
}
}
}
Prim 算法
核心思想:维护最小生成树的一部分。
思路:维护两个点集——已确定属于最小生成树 未确定的。每次找出端点属于两个集合且边权最小的边连接,再把该条边原属于未确定点集的端点放入已确定的中。直到未确定点集为空。
代码:
int map[N][N], d[M];
bool vis[N];
void prim() {
memset(d, 0x3f, sizeof(d));
memset(vis, 0, sizeof(vis));
d[1] = 0;
for(int i = 1; i < n; i ++) {
int x = 0;
for(int j = 1; j <= n; j ++) {
if(!vis[j] && (!x || d[j] < d[x])) x = j;
}
vis[x] = 1;
for(int y = 1; y <= n; y ++) {
if(!vis[y]) d[y] = min(d[y], a[x][y]);
}
}
}
int main() {
prim();
for(int i = 2; i <= n; i ++) ans[i] += d[i];
return 0;
}
通过代码也不难发现, 算法更适用于稠密图,尤其是完全图。
应用
走廊泼水节
给定一棵 N 个节点的树,要求增加若干条边,把这棵树扩充为完全图,并满足图的唯一最小生成树仍然是这棵树。
求增加的边的权值总和最小是多少。
注意: 树中的所有边权均为整数,且新加的所有边权也必须为整数。
分析 算法中最小生成树的形成过程,不难得到,当一条边被确定为树边时,其实就是两个并查集的联通,而这两条边之所以可以连通,就是因为不存在连接两个集合且边权更小的边了。
因此,用数组 维护每个并查集的点数,在两个集联通所贡献的答案数就是,。
点击查看代码
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
const int N = 6e3 + 5;
int t, n, fa[N], siz[N];
ll ans;
struct node {
int x, y, val;
}e[N];
bool cmp(node a, node b) {
return a.val < b.val;
}
int find(int x) {
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
void connect(int x, int y) {
x = find(x), y = find(y);
fa[y] = x;
siz[x] += siz[y];
}
int main() {
int u, v;
scanf("%d", &t);
while(t --) {
ans = 0;
scanf("%d", &n);
for(int i = 1; i <= n; i ++) fa[i] = i, siz[i] = 1;
for(int i = 1; i < n; i ++) scanf("%d %d %d", &e[i].x, &e[i].y, &e[i].val);
sort(e + 1, e + n, cmp);
for(int i = 1; i < n; i ++) {
u = find(e[i].x), v = find(e[i].y);
if(u != v) {
ans += (ll)(e[i].val + 1) * (siz[u] * siz[v] - 1);
connect(u, v);
}
}
printf("%lld\n", ans);
}
return 0;
}
野餐规划
一群小丑演员,以其出色的柔术表演,可以无限量的钻进同一辆汽车中,而闻名世界。
现在他们想要去公园玩耍,但是他们的经费非常紧缺。
他们将乘车前往公园,为了减少花费,他们决定选择一种合理的乘车方式,可以使得他们去往公园需要的所有汽车行驶的总公里数最少。
为此,他们愿意通过很多人挤在同一辆车的方式,来减少汽车行驶的总花销。
由此,他们可以很多人驾车到某一个兄弟的家里,然后所有人都钻进一辆车里,再继续前进。
公园的停车场能停放的车的数量有限,而且因为公园有入场费,所以一旦一辆车子进入到公园内,就必须停在那里,不能再去接其他人。
现在请你想出一种方法,可以使得他们全都到达公园的情况下,所有汽车行驶的总路程最少。
点击查看代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<map>
#include<cmath>
#include<iostream>
using namespace std;
const int N = 2e6 + 5;
int n, tot, cnt, rt, S, res, ans = 1e9, dp[N], fa[N];
struct node {
int x, y, dis;
}e[N];
bool cmp(node a, node b) {
return a.dis < b.dis;
}
map<string, int> mp;
string s, t;
int find(int x) {
return fa[x] == x ? x : find(fa[x]);
}
void dfs(int x) {
if(dp[rt] > S) return;
int tag = 1;
for(int i = 1; i < tot; i ++) {
if(find(i) != find(i + 1)) tag = 0;
}
if(tag) {
ans = min(ans, res);
return;
}
if(x == n + 1) return;
int u = find(e[x].x), v = find(e[x].y);
if(u == v) {
dfs(x + 1);
return;
}
dp[e[x].y] ++, dp[e[x].x] ++;
fa[u] = v;
res += e[x].dis;
dfs(x + 1);
dp[e[x].y] --, dp[e[x].x] --;
fa[u] = u;
res -= e[x].dis;
if(e[x].x == rt || e[x].y == rt) dfs(x + 1);
}
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i ++) {
cin >> s >> t >> e[i].dis;
if(!mp.count(s)) mp[s] = ++tot;
if(!mp.count(t)) mp[t] = ++tot;
e[i].x = mp[s], e[i].y = mp[t];
}
rt = mp["Park"];
for(int i = 1; i <= tot; i ++) fa[i] = i;
sort(e + 1, e + 1 + n, cmp);
scanf("%d", &S);
dfs(1);
printf("Total miles driven: %d", ans);
return 0;
}
沙漠之王
大卫大帝刚刚建立了一个沙漠帝国,为了赢得他的人民的尊重,他决定在全国各地建立渠道,为每个村庄提供水源。
与首都相连的村庄将得到水资源的浇灌。
他希望构建的渠道可以实现单位长度的平均成本降至最低。
换句话说,渠道的总成本和总长度的比值能够达到最小。
他只希望建立必要的渠道,为所有的村庄提供水资源,这意味着每个村庄都有且仅有一条路径连接至首都。
他的工程师对所有村庄的地理位置和高度都做了调查,发现所有渠道必须直接在两个村庄之间水平建造。
由于任意两个村庄的高度均不同,所以每个渠道都需要安装一个垂直的升降机,从而使得水能够上升或下降。
建设渠道的成本只跟升降机的高度有关,换句话说只和渠道连接的两个村庄的高度差有关。
需注意,所有村庄(包括首都)的高度都不同,不同渠道之间不能共享升降机。
先二分答案,然后每次新建一课最小生成树检验。
因为图比较稠密,所以用 比较好。
点击查看代码
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iostream>
using namespace std;
const int N = 1005;
const double eps = 1e-6, inf = 1e18;
int n, x[N], y[N], h[N];
double dis[N];
bool tag[N];
int pow(int x) {
return x * x;
}
double get_dis(int a, int b) {
return sqrt(pow(x[a] - x[b]) + pow(y[a] - y[b]));
}
bool check(double mid) {
fill(dis, dis + 1 + n, inf);
memset(tag, 0, sizeof(tag));
dis[1] = 0;
double ans = 0;
for(int i = 1; i <= n; i ++) {
int t = -1;
for(int j = 1; j <= n; j ++) {
if(!tag[j] && (t == -1 || dis[j] < dis[t])) t = j;
}
tag[t] = 1, ans += dis[t];
for(int j = 1; j <= n; j ++) {
double tmp = abs(h[t] - h[j]) - mid * get_dis(t, j) + eps;
if(!tag[j] && dis[j] > tmp) dis[j] = tmp - eps;
}
}
return ans >= 0.0;
}
int main() {
while(~scanf("%d", &n) && n) {
for(int i = 1; i <= n; i ++) {
scanf("%d %d %d", &x[i], &y[i], &h[i]);
}
double l = 0, r = 10000001.0;
while((r - l) > eps) {
double mid = (l + r) / 2;
if(check(mid)) l = mid;
else r = mid;
}
printf("%.3f\n", l);
}
return 0;
}
黑暗城堡
在顺利攻破 Lord lsp 的防线之后,lqr 一行人来到了 Lord lsp 的城堡下方。
Lord lsp 黑化之后虽然拥有了强大的超能力,能够用意念力制造建筑物,但是智商水平却没怎么增加。
现在 lqr 已经搞清楚黑暗城堡有 N 个房间,M 条可以制造的双向通道,以及每条通道的长度。
lqr 深知 Lord lsp 的想法,为了避免每次都要琢磨两个房间之间的最短路径,Lord lsp 一定会把城堡修建成树形的。
但是,为了尽量提高自己的移动效率,Lord lsp 一定会使得城堡满足下面的条件:
设 D[i] 为如果所有的通道都被修建,第 i 号房间与第 1 号房间的最短路径长度;而 S[i] 为实际修建的树形城堡中第 i 号房间与第 1 号房间的路径长度;要求对于所有整数 i,有 S[i]=D[i] 成立。
为了打败 Lord lsp,lqr 想知道有多少种不同的城堡修建方案。
点击查看代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
#define int long long
const int N = 1005, M = 2e6;
const int mod = (1ll << 31) - 1;
bool vis[N];
int n, m, cnt[N], dis[N], ans = 1ll;
int tot, head[N], to[M], nex[M], w[M];
void add(int x, int y, int z) {
to[++tot] = y, nex[tot] = head[x], head[x] = tot, w[tot] = z;
to[++tot] = x, nex[tot] = head[y], head[y] = tot, w[tot] = z;
}
void djs() {
queue<int> q;
memset(dis, 0x3f, sizeof(dis));
dis[1] = 0, q.push(1), vis[1] = 1;
int now, ver;
while(!q.empty()) {
now = q.front(), q.pop(), vis[now] = 0;
for(int i = head[now]; i; i = nex[i]) {
ver = to[i];
if(dis[ver] > dis[now] + w[i]) {
dis[ver] = dis[now] + w[i];
if(!vis[ver]) q.push(ver), vis[ver] = 1;
}
}
}
}
signed main() {
scanf("%lld %lld", &n, &m);
int u, v, z;
for(int i = 1; i <= m; i ++) {
scanf("%lld %lld %lld", &u, &v, &z);
add(u, v, z);
}
djs();
for(int i = 1; i <= n; i ++) {
for(int j = head[i]; j; j = nex[j]) {
v = to[j];
if(dis[v] == dis[i] + w[j]) cnt[v] ++;
}
}
for(int i = 1; i <= n; i ++) {
if(cnt[i]) ans = ans * cnt[i] % mod;
}
printf("%lld", ans);
return 0;
}
本文作者:Spring-Araki
本文链接:https://www.cnblogs.com/Spring-Araki/p/16463554.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步