《算法竞赛进阶指南》0x62 最小生成树

news/2025/1/1 8:07:11/

0x62 最小生成树

走廊泼水节

题意:

给定一棵树,将这棵树加边,扩充为完全图,使完全图的最小生成树为原来的树,询问增加的边权值总和最小是多少

解析:

考虑 kruskal 产生最小生成树的过程:选择当前连接两个连通块边权最小的边。

所以,对于最小生成树上的边,该边一定是两个连通块之间唯一的最短边,设该边权值为 w w w,则两个连通块之间的边权值一定大于 w w w,最小为 w + 1 w+1 w+1

设树上边连接的两个连通块的大小分别为 s i z ( x ) , s i z ( y ) siz(x),siz(y) siz(x),siz(y)。则两个连通块之间有 s i z ( x ) × s i z ( y ) siz(x)\times siz(y) siz(x)×siz(y) 条边,其中有一条是树上的边。对答案的贡献为 ( s i z ( x ) × s i z ( y ) − 1 ) × ( w + 1 ) (siz(x)\times siz(y)-1) \times (w+1) (siz(x)×siz(y)1)×(w+1)

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define fi first
#define se second
#define debug(x) cerr << #x << ": " << (x) << endl
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
const int maxn = 1e5+10;
const int maxm = 1e5+10;
const int INF = 0x3f3f3f3f;
typedef pair<int, int> pii;struct edge{int u, v, w;edge(){}edge(int u, int v, int w) : u(u), v(v), w(w){}bool operator < (const edge &b) const{return w < b.w;}
}e[maxn];
ll fa[maxn], siz[maxn];
void init(int MAX){for(int i = 1; i <= MAX; i++){fa[i] = i;siz[i] = 1;}
}
int find(int x){return x == fa[x] ? fa[x] : fa[x] = find(fa[x]);
}
void merge(int x, int y){int fx = find(x);int fy = find(y);if(fx != fy){fa[fx] = fy;siz[fy] += siz[fx];}
}
int n;
void solve(){cin >> n;init(n);for(int i = 1, u, v, w; i < n; i++){cin >> u >> v >> w;e[i] = edge(u, v, w);}sort(e+1, e+n);ll ans = 0;for(int i = 1; i < n; i++){int u = e[i].u;int v = e[i].v;int w = e[i].w;int fx = find(u);int fy = find(v);if(fx == fy)continue;ans += (siz[fx]*siz[fy]-1) * (w+1);merge(fx, fy);}cout << ans << endl;
}
int main(){ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);int T;cin >> T;while(T--)solve();return 0;
}


野餐规划

题意:

求图的最小生成树,节点 1 1 1 的度最大为 s s s

解析:

首先删去节点 1 1 1,原图变成 t t t 个连通块 ( t ≥ 1 ) (t \ge 1) (t1)

注意到最小生成树的子树也一定是对应子图的最小生成树。所以在每个连通块中求出最小生成树。

然后把 1 1 1 节点向每个连通块选最小的边连边,得到一棵生成树,但这棵生成树不一定最小。

对一个连通块而言,可以增加一条 1 1 1 号节点连向该连通块的边,然后删去连通块的最小生成树上的一条边,有可能会使权值和更小。

可以求出一个连通块中到 1 1 1 号节点的路径上的边权最大值。枚举 1 1 1 号节点的边 ( 1 , p ) (1,p) (1,p) ,如果不在最小生成树上,则可以加边 ( 1 , p ) (1,p) (1,p),删去边 ( u , v ) (u,v) (u,v) ( u , v ) (u,v) (u,v) 是节点 p p p 到 节点 1 1 1 路径上边权最大的边。

直到节点 1 1 1 的度为 s s s,或者加边之后边权不会变小。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define fi first
#define se second
#define mkp(a, b) make_pair((a), (b)) 
#define debug(x) cerr << #x << ": " << (x) << endl
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
const int maxn = 1e3+10;
const int maxm = 1e5+10;
const int INF = 0x3f3f3f3f;
typedef pair<int, int> pii;int head[maxn], tot;
struct Edge{int to, nxt, w;
}e[maxm << 1]; 
int g[maxn][maxn];
struct node{int a, b, w;node(){}node(int a, int b, int w) : a(a), b(b), w(w){}bool operator < (const node &rhs) const{return w < rhs.w;} 
}f[maxn]; vector<node> edg;
void add(int a, int b, int c){e[++tot].nxt = head[a];e[tot].to = b;e[tot].w = c;head[a] = tot;
}int fa[maxn];
int find(int x){return x == fa[x] ? x : fa[x] = find(fa[x]);
}
void merge(int x, int y){int fx = find(x);int fy = find(y);if(fx != fy)fa[fx] = fy;
}int cnt; //连通块个数 
int bel[maxn]; // 哪个连通块
vector<int> block[maxn]; 
map<pii, bool> tree;
map<string, int> id;
int n, m, s;
void dfs(int u){bel[u] = cnt;block[cnt].push_back(u);for(int i = head[u]; i; i = e[i].nxt){int v = e[i].to;if(!bel[v] && v != 1)dfs(v);}
}
int kruskal(){sort(edg.begin(), edg.end());int res = 0;for(int i = 0; i < edg.size(); i++){int fx = find(edg[i].a);int fy = find(edg[i].b);if(fx != fy && fx != 1 && fy != 1){res += edg[i].w;fa[fx] = fy;tree[mkp(edg[i].a, edg[i].b)] = 1;tree[mkp(edg[i].b, edg[i].a)] = 1;}}return res;
}void dp(int u, int p){for(int i = head[u]; i; i = e[i].nxt){int v = e[i].to;if(v == p || !tree[mkp(u, v)])continue;if(f[u].w > e[i].w)f[v] = edg[u];elsef[v] = node(u, v, e[i].w);dp(v, u);}
}
int main(){ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);cin >> m;id["Park"] = ++n;string a, b;int c;for(int i = 1; i <= m; i++){cin >> a >> b >> c;if(!id.count(a))id[a] = ++n;if(!id.count(b))id[b] = ++n;add(id[a], id[b], c);add(id[b], id[a], c);g[id[a]][id[b]] = g[id[b]][id[a]] = c;edg.push_back(node(id[a], id[b], c));}cin >> s;for(int i = 1; i <= n; i++)fa[i] = i;for(int i = 2; i <= n; i++){if(!bel[i]){++cnt;dfs(i);}} int ans = kruskal();for(int i = 1; i <= cnt; i++){int tmp = INF;int p = -1;for(auto u : block[i]){if(g[1][u] != 0 && tmp > g[1][u]){tmp = g[1][u];p = u;}}ans += tmp;tree[mkp(1, p)] = 1;tree[mkp(p, 1)] = 1;}while(s > cnt){s--;for(int i = 0; i <= n; i++)f[i].w = -1;dp(1, 0);int tmp = -1, p = -1;for(int i = head[1]; i; i = e[i].nxt){int v = e[i].to;if(tmp < f[v].w - e[i].w){tmp = f[v].w - e[i].w;p = v;}}if(tmp == -1)break;ans -= tmp;int u = f[p].a, v = f[p].b;tree[mkp(u, v)] = tree[mkp(v, u)] = false;tree[mkp(1, p)] = tree[mkp(p, 1)] = true;}cout<<"Total miles driven: "<<ans;return 0;
}


沙漠之王

题意:

边有成本和长度两个参数。求图的生成树,使边的总成本与边的总长度的比值最小

解析:

0/1分数规划。

设边的成本为 a i a_i ai,长度为 b i b_i bi

设最小值的估计值为 L L L,判断是否存在一棵生成树,满足 ∑ ( a i − L × b i ) ≤ 0 \sum(a_i-L\times b_i) \le 0 (aiL×bi)0

  • 如果存在一棵生成树,使 ∑ ( a i − L × b i ) ≤ 0 \sum(a_i-L\times b_i) \le 0 (aiL×bi)0 成立,变形得 ∑ a i ∑ b i ≤ L \frac{\sum a_i}{\sum b_i} \le L biaiL。则 L L L 比最小值小

  • 如果对于任意生成树,都有 ∑ ( a i − L × b i ) > 0 \sum(a_i-L\times b_i) > 0 (aiL×bi)>0,则 L L L 比最小值大

L L L 关于解得存在具有单调性。所以可以二分这个 L L L,然后判断最小生成树的权值和是否大于零

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define fi first
#define se second
#define debug(x) cerr << #x << ": " << (x) << endl
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
const int maxn = 1e3+10;
const int maxm = 1e5+10;
const int INF = 0x3f3f3f3f;
const double DINF = 1e18;
const double eps = 1e-8; 
typedef pair<int, int> pii;double a[maxn][maxn];
double b[maxn][maxn];
double dis[maxn];
int vis[maxn];
struct point{double x, y, z;
}p[maxn];
double getdis(point i, point j){return (double)sqrt((i.x-j.x)*(i.x-j.x) + (i.y-j.y)*(i.y-j.y));
}
int n; 
int check(double x){memset(vis, 0, sizeof(vis));fill(dis, dis+1+n, DINF);dis[1] = 0;double res = 0;for(int i = 1; i <= n; i++){double tmp = DINF;int po = 1;for(int j = 1; j <= n; j++){if(!vis[j] && tmp > dis[j]){tmp = dis[j];po = j;}}vis[po] = 1;res += dis[po];for(int j = 1; j <= n; j++){if(!vis[j] && dis[j] > fabs(p[po].z-p[j].z) - x*getdis(p[po], p[j]))dis[j] = fabs(p[po].z-p[j].z) - x*getdis(p[po], p[j]);}}return res >= 0.0;
}
void solve(){for(int i = 1; i <= n; i++){cin >> p[i].x >> p[i].y >> p[i].z;}double l = 0, r = 10.0;double ans = 0;while(fabs(r-l) > eps){double mid = (l+r)/2;if(check(mid)){ans = mid;l = mid;}elser = mid;}cout << fixed << setprecision(3) << ans << endl;return;
}
int main(){ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);while(1){cin >> n;if(n == 0)break;solve(); }return 0;
}

黑暗城堡

题意:

求生成树的方案数,使该方案节点 1 1 1 与任意节点的距离 与 原图节点 1 1 1 与任意节点的最短距离相等

解析:

询问最短路径树的个数。

Dijkstra 求出单源最短路。然后枚举每一个节点,有多少条边到这个点的路径长度为最短路径。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define int ll
#define fi first
#define se second
#define debug(x) cerr << #x << ": " << (x) << endl
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
const int maxn = 1e5+10;
const int maxm = 1e6+10;
const int INF = 0x3f3f3f3f;
const ll mod = (1ll << 31) - 1;
typedef pair<int, int> pii;int head[maxn], tot;
int dis[maxn], vis[maxn];
struct edge{int to, nxt, w;
}e[maxm << 1];
int n, m;
int cnt[maxn];
void add(int a, int b, int c){e[++tot].nxt = head[a];e[tot].to = b;e[tot].w = c;head[a] = tot;
}
void Dijkstra(int st){memset(dis, INF, sizeof(dis));dis[st] = 0;memset(vis, 0, sizeof(vis));for(int i = 1; i < n; i++){int p = -1, d = INF;for(int j = 1; j <= n; j++){if(vis[j])continue;if(dis[j] < d){d = dis[j];p = j;}}vis[p] = 1;for(int j = head[p]; j; j = e[j].nxt){int v = e[j].to;dis[v] = min(dis[v], dis[p] + e[j].w);}}
}
/*
ll prim(int st){memset(vis, 0, sizeof(vis));vis[st] = 1;ll res = 1;for(int i = 1; i < n; i++){int p = -1;int d = INF;for(int j = 2; j <= n; j++){if(vis[j])continue;if(dis[j] < d){d = dis[j];p = j;}}vis[p] = 1;ll cnt = 0;for(int j = head[p]; j; j = e[j].nxt){int v = e[j].to;if(dis[p] == dis[v] + e[j].w)cnt++;}res = res * cnt % mod;}return res;
}*/
signed main(){ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);cin >> n >> m;for(int i = 1, u, v, w; i <= m; i++){cin >> u >> v >> w;add(u, v, w);add(v, u, w);		}Dijkstra(1);for(int u = 1; u <= n; u++){for(int i = head[u]; i; i = e[i].nxt){int v = e[i].to;if(dis[v] == dis[u] + e[i].w)cnt[v]++;}} ll res = 1;for(int i = 2; i <= n; i++)res = (res * cnt[i]) % mod;	cout << res << endl;return 0;
}

http://www.ppmy.cn/news/79698.html

相关文章

哈希表(散列表)详解

&#x1f495;**今天的每一秒都是珍贵的&#xff0c;因为它永远不会再次出现。**&#x1f495; &#x1f43c;作者&#xff1a;不能再留遗憾了&#x1f43c; &#x1f386;专栏&#xff1a;Java学习&#x1f386; &#x1f697;本文章主要内容&#xff1a;深入理解哈希表&#…

1688阿里巴巴中国站按关键字搜索抓取新品数据API接口展示示例(封装可高并发)(Java系列)

一、电商平台上新的重要性 电商平台上新非常重要。 首先&#xff0c;持续的新品上线可以吸引更多的用户访问平台和留存用户的兴趣。新品可以激发用户想要知道更多、购买更多的欲望&#xff0c;从而提高用户的使用频率和转化率。此外&#xff0c;新品上线也可以使电商平台更具…

针对KF状态估计的电力系统虚假数据注入攻击研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Java中的equals方法详解,Java中的toString方法

目录 第一章、Java中的两种比较方式&#xff1a;比较和equals比较1&#xff09;Java中的 ""比较2&#xff09;Java中的 equals比较 第二章、重写toString方法1&#xff09;toString方法2&#xff09;重写equals和toString方法快捷键 第一章、Java中的两种比较方式&am…

python正则表达式-正则基础

目录 一、任一元素 二、匹配特定的字符类别 1、\d \w 三、多个元素 1、两位元素 [][] 2、* &#xff1f; 3、重复次数 {} 4、位置匹配 ^ $ 5、子表达式&#xff08;&#xff09; 一、任一元素 []&#xff1a;1、[ab] 匹配a或b&#xff1b; 2、[0-9] 匹配任意一个数字&…

Linux下V4l2框架编程_USB摄像头数据采集

Linux内核版本:3.5.0 1.1 V4L2简介 v4L2是针对uvc免驱usb设备的编程框架,主要用于采集usb摄像头等。 这篇文章介绍V4L2框架读取摄像头数据的流程,介绍ioctl常用的命令参数,以及各种摄像头相关的结构体成员含义,最终完成数据采集。 编程模式如下: V4l2支持多种设备,它可…

六级备考25天|CET-6|听力第四讲|篇章满分技巧|全文听写带练|2022年6月考题12-15题|16:10~17:10

目录 1. 读题步骤 &#xff08;1&#xff09;听力前 &#xff08;2&#xff09;听力中 2. 复现听力原文 问题12 overturn 推翻 universal theory 通用的理论 evasive 不坦率的 模棱两可的 逃避的 规避的 问题13 问题14 nonetheless 无论如何 rigorously 严…

pix2pixHD代码---readme

1&#xff1a;基础配置 要求大于等于11G的显卡&#xff0c;安装pytorch&#xff0c;下载代码。 2&#xff1a;测试 dataset文件中放的是一些例子&#xff0c;下载cityscape的预训练权重&#xff0c;放入到checkpoints文件夹下&#xff0c;测试模型。测试结果放在results文件夹…