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

这篇具有很好参考价值的文章主要介绍了《算法竞赛进阶指南》0x62 最小生成树。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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];
		else
			f[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,答案为 M I N MIN MIN,判断是否存在一棵生成树,满足 ∑ ( a i − L × b i ) < 0 \sum(a_i-L\times b_i) < 0 (aiL×bi)<0

  • 如果存在一棵生成树,使 ∑ ( a i − L × b i ) < 0 \sum(a_i-L\times b_i) < 0 (aiL×bi)<0 成立,变形得 ∑ a i ∑ b i < L \frac{\sum a_i}{\sum b_i} < L biai<L。则 M I N < L MIN < L MIN<L

  • 如果对于任意生成树,都有 ∑ ( a i − L × b i ) ≥ 0 \sum(a_i-L\times b_i) \ge 0 (aiL×bi)0,即 ∑ a i ∑ b i ≥ L \frac{\sum a_i}{\sum b_i} \ge L biaiL ,则 M I N ≥ L MIN \ge L MINL

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;
		}
		else
			r = 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 求出单源最短路。然后枚举每一个节点,有多少条边到这个点的路径长度为最短路径。文章来源地址https://www.toymoban.com/news/detail-458251.html

代码:

#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;
}

到了这里,关于《算法竞赛进阶指南》0x62 最小生成树的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 算法刷刷刷|动态规划篇|509.斐波那契数| 70.爬楼梯| 746.使用最小花费爬楼梯| 62.不同路径| 63不同路径2| 343.正数拆分 | 96.不同的二叉搜索树

    509. 斐波那契数 斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是: F(0) = 0,F(1) = 1 F(n) = F(n - 1) + F(n - 2),其中 n 1 给定 n ,请计算 F(n) 。 70.爬楼梯 746.使用最小花费爬楼梯 给你一个整数

    2023年04月23日
    浏览(56)
  • 算法竞赛备赛进阶之数位DP训练

    数位DP的思想就是对每一位进行DP,计算时记忆化每一位可以有的状态,其作用是减少运算时间,避免重复计算。 数位DP是一种计数用的DP,一般就是要统计一个区间[A,B]内满足一些条件数的个数。 以1e9甚至1e18、1e100的问题为例,因为在统计情况下有很多重复的计算,数位DP实

    2024年01月16日
    浏览(48)
  • 考研算法复试刷题19天:Prim算法求最小生成树 【prim,最小生成树】

    参考博客:图解:什么是最小生成树? - 知乎 (zhihu.com)  总结下来的过程就是,一张图,我们将他化为树的形式,也就是生成树。那么最小生成树有是啥呢? 所谓一个 带权图 的最小生成树,就是原图中边的权值最小的生成树 ,所谓最小是指边的权值之和小于或者等于其它

    2024年02月07日
    浏览(60)
  • 算法竞赛入门【码蹄集进阶塔335题】(MT2281-2285)

    为什么突然想学算法了? 用较为“官方”的语言讲 ,是因为算法对计算机科学的所有分支都非常重要。 在绝大多数的计算机科学分支领域中,要想完成任何实质性的工作,理解算法的基础知识并掌握与算法密切相关的数据结构知识是必不可少的。 但从实际而言 ,是因为当

    2024年02月07日
    浏览(38)
  • 算法竞赛入门【码蹄集进阶塔335题】(MT2176-2200)

    为什么突然想学算法了? 用较为“官方”的语言讲 ,是因为算法对计算机科学的所有分支都非常重要。 在绝大多数的计算机科学分支领域中,要想完成任何实质性的工作,理解算法的基础知识并掌握与算法密切相关的数据结构知识是必不可少的。 但从实际而言 ,是因为当

    2024年02月09日
    浏览(40)
  • 算法竞赛入门【码蹄集进阶塔335题】(MT2051-2075)

    为什么突然想学算法了? 用较为“官方”的语言讲 ,是因为算法对计算机科学的所有分支都非常重要。 在绝大多数的计算机科学分支领域中,要想完成任何实质性的工作,理解算法的基础知识并掌握与算法密切相关的数据结构知识是必不可少的。 但从实际而言 ,是因为当

    2023年04月15日
    浏览(43)
  • 算法竞赛入门【码蹄集进阶塔335题】(MT2201-2225)

    为什么突然想学算法了? 用较为“官方”的语言讲 ,是因为算法对计算机科学的所有分支都非常重要。 在绝大多数的计算机科学分支领域中,要想完成任何实质性的工作,理解算法的基础知识并掌握与算法密切相关的数据结构知识是必不可少的。 但从实际而言 ,是因为当

    2024年02月08日
    浏览(32)
  • 算法竞赛入门【码蹄集进阶塔335题】(MT2271-2275)

    为什么突然想学算法了? 用较为“官方”的语言讲 ,是因为算法对计算机科学的所有分支都非常重要。 在绝大多数的计算机科学分支领域中,要想完成任何实质性的工作,理解算法的基础知识并掌握与算法密切相关的数据结构知识是必不可少的。 但从实际而言 ,是因为当

    2024年02月07日
    浏览(32)
  • 算法竞赛入门【码蹄集进阶塔335题】(MT2126-2150)

    为什么突然想学算法了? 用较为“官方”的语言讲 ,是因为算法对计算机科学的所有分支都非常重要。 在绝大多数的计算机科学分支领域中,要想完成任何实质性的工作,理解算法的基础知识并掌握与算法密切相关的数据结构知识是必不可少的。 但从实际而言 ,是因为当

    2023年04月19日
    浏览(36)
  • 算法竞赛入门【码蹄集进阶塔335题】(MT2301-2305)

    为什么突然想学算法了? 用较为“官方”的语言讲 ,是因为算法对计算机科学的所有分支都非常重要。 在绝大多数的计算机科学分支领域中,要想完成任何实质性的工作,理解算法的基础知识并掌握与算法密切相关的数据结构知识是必不可少的。 但从实际而言 ,是因为当

    2024年02月06日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包