新的开始(图论、最小生成树、kruskal)

1488:新的开始


时间限制: 1000 ms         内存限制: 65536 KB
提交数: 1784     通过数: 943

【题目描述】

发展采矿业当然首先得有矿井,小 FF 花了上次探险获得的千分之一的财富请人在岛上挖了 nn 口矿井,但他似乎忘记考虑的矿井供电问题……

为了保证电力的供应,小 FF 想到了两种办法:

在这一口矿井上建立一个发电站,费用为 vv(发电站的输出功率可以供给任意多个矿井)。

将这口矿井与另外的已经有电力供应的矿井之间建立电网,费用为 pp。

小 FF 希望身为「NewBe_One」计划首席工程师的你帮他想出一个保证所有矿井电力供应的最小花费。

【输入】

第一行一个整数 nn,表示矿井总数。

第 2∼n+12∼n+1 行,每行一个整数,第 ii 个数 vivi 表示在第 ii 口矿井上建立发电站的费用。

接下来为一个 n×nn×n 的矩阵 pp,其中 pi,jpi,j 表示在第 ii 口矿井和第 jj 口矿井之间建立电网的费用(数据保证有pi,j=pj,ipi,j=pj,i​ ,且 pi,i=0pi,i=0。

【输出】

输出仅一个整数,表示让所有矿井获得充足电能的最小花费。

【输入样例】

4  
5  
4 
4  
3  
0 2 2 2  
2 0 3 3  
2 3 0 4  
2 3 4 0

【输出样例】

9

【提示】

样例解释

小 FF 可以选择在 44 号矿井建立发电站然后把所有矿井都不其建立电网,总花费是 3+2+2+2=93+2+2+2=9。

数据范围:

对于 30% 的数据:1≤n≤501≤n≤50;

对于 100% 的数据:1≤n≤300,0≤vi,pi,j≤1051≤n≤300,0≤vi,pi,j≤105​​ 。

 题目链接:信息学奥赛一本通(C++版)在线评测系统 (ssoier.cn)http://ybt.ssoier.cn:8088/problem_show.php?pid=1488

思路1: 只需要在合并两个点的时候判断连接这两点边花费的值是否小于其中一个点原本花费的值

#include<bits/stdc++.h>
using namespace std; using ll = long long;

const int N = 1e3 + 10, mod = 1e9 + 7, INF = 0x3f3f3f3f;

int n, m, w[N], g[N][N], f[N];

class Edge {
public:
	int x, y, d;
	Edge(int a, int b, int  c) :x(a), y(b), d(c) {}
	bool operator>(const Edge& t) const {
		return d > t.d;
	}
};

priority_queue<Edge, vector<Edge>, greater<Edge>> edges;

int getf(int x) {
	return x == f[x] ? x : f[x] = getf(f[x]);
}

bool merge(int u, int v) {
	int t1 = getf(u), t2 = getf(v);
	if (t1 == t2 || max(w[t1], w[t2]) <= g[u][v])
		return false;  // 如果连接这两点的花费大于这两点任何一点自己建电站的花费则不连接
	if (t1 > t2) swap(t1, t2);
	f[t2] = t1;
	w[t2] = w[t1] = min(w[t2], w[t1]);  // 每一点的花费为他所在的集合中建电站花费最小的点的花费
	return true;
}

int kruskal() {
	ll res = 0;
	while (edges.size()) {
		auto [x, y, d] = edges.top();
		edges.pop();
		if (merge(x, y)) {
			res += d;
		}
	}
	set<int> eci;
	for (int i = 1; i <= n; i++) {
		eci.emplace(getf(i));
	}
	for (auto i : eci) {
		res += w[i];
	}
	return res;
}

void init() {
	cin >> n;
	for (int i = 1; i <= n; i++)
		cin >> w[i], f[i] = i;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			cin >> g[i][j];
			edges.emplace(i, j, g[i][j]);
		}
	}
	return;
}

void solve() {
	cout << kruskal();
	return;
}

int main(void) {
	ios::sync_with_stdio(0); cin.tie(0); cout << setprecision(6) << fixed;
	int TT = 1;
	//cin >> TT;
	for (int ii = 1; ii <= TT; init(), solve(), ii++, cout << "\n") {}
	return 0;
}

思路2 :建立一个虚拟源点,把在每个点建电站的花费看成是虚拟源点连接该点的花费,把包括虚拟源点所在的所有点跑一遍最小生成树便是答案。

#include<bits/stdc++.h>
using namespace std; using ll = long long;

const int N = 1e3 + 10, mod = 1e9 + 7, INF = 0x3f3f3f3f;

int n, m, w[N], g[N][N], dist[N];

bool st[N];

int prim() {
	memset(dist, 0x3f, sizeof dist);
	ll res = 0;
	dist[0] = 0;
	for (int i = 0; i <= n; i++) {
		int t = -1;
		for (int j = 0; j <= n; j++) {
			if (!st[j] && (t == -1 || dist[j] < dist[t]))
				t = j;
		}
		res += dist[t];
		st[t] = 1;
		for (int j = 0; j <= n; j++) {
			dist[j] = min(dist[j], g[t][j]);
		}
	}
	return res;
}

void init() {
	cin >> n;
	for (int i = 1; i <= n; i++)  // 把每个点建电站的花费看成 0点 到该点连接的花费,0 点为虚拟源点
		cin >> g[i][0], g[0][i] = g[i][0];
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			cin >> g[i][j];
		}
	}
	return;
}

void solve() {
	cout << prim();
	return;
}

int main(void) {
	ios::sync_with_stdio(0); cin.tie(0); cout << setprecision(6) << fixed;
	int TT = 1;
	//cin >> TT;
	for (int ii = 1; ii <= TT; init(), solve(), ii++, cout << "\n") {}
	return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值