Farmer John 的农场缺水了。
他决定将水引入到他的 n 个牧场。他准备通过挖若干井,并在各块田中修筑水道来连通各块田地以供水。在第 i 号田中挖一口井需要花费 Wi 元。连接 i 号田与 j 号田需要 Pi,j(Pj,i=Pi,j)元。
请求出 FJ 需要为使所有农场都与有水的农场相连或拥有水井所需要的最少钱数。
输入格式
第一行为一个整数 n。
接下来 n 行,每行一个整数 Wi。
接下来 n 行,每行 n 个整数,第 i 行的第 j 个数表示连接 i 号田和 j 号田需要的费用 Pi,j。
输出格式
输出最小开销。
输入输出样例
输入 #1复制
4 5 4 4 3 0 2 2 2 2 0 3 3 2 3 0 4 2 3 4 0输出 #1复制
9说明/提示
对于 100%100% 的数据,1≤n≤300,1≤Wi≤1e5,0≤Pi,j≤1e5。
在第i个点挖一口井需要w[i]的代价,而连起来则需要P[i][j]的代价,这应该是该题最最最核心的一句话了,可能有的同学会想,那我这个点到底是要去挖一口井还是连一口井,是不是需要写个if判断一下如果w[i] < min(p[i])那我就直接挖井而不需要连了,其实这样的考虑是复杂了的,我们抛开这个问题不谈,可以感觉出来,这其实就是一道非常经典的最小生成树算法的模板题,那么我们要怎么把难点抽象成经典问题,其实我们可以把地下水看成一个节点,那么整个图的节点就不是n个了而是n+1个节点,而在第 i 号田中挖一口井需要花费 Wi 元,wi元其实就是农田与地下水连接所花费的代价,好的,那么一眼顶针,鉴定为纯纯克鲁斯卡尔。
CODE:
注:虽然笔者的代码能力可能稀烂,但是其实这些define都是一些比较常规的方法,同学们耐心一下很容易就可以读懂
#include<bits/stdc++.h>
using namespace std;
#define FOR(i, a, b) for(int i = a;i<=b;i++)
#define ROF(i, a, b) for(int i = a;i>=b;i--)
#define foreach(i, b) FOR(i,1,b)
#define rofeach(i, b) ROF(i,b,1)
#define speed cin.tie(0),cout.tie(0),ios::sync_with_stdio(false)
#define AC cout << (ans) << endl
#define GG cout << "-1" << endl
#define finish return 0
const int N = 100007;
int n;
int w[N];
int f[N];
int ans;
//并查集find
int find(int x){
return f[x] == x ? f[x] : f[x] = find(f[x]);
}
struct node{
int u,v,w;
}e[N];
//比较函数
bool cmp(node a,node b){
return a.w < b.w;
}
int idx;
//建图
void add(int a,int b,int c){
e[++idx].u = a;
e[idx].v = b;
e[idx].w = c;
}
int main(){
speed;
cin >> n;
foreach(i,n) cin >> w[i],f[i] = i;
foreach(i,n){
foreach(j,n){
int x;
cin >> x;
if(i!=j) add(i,j,x);
}
}
//扩展,将农田与地下水连接起来
foreach(i,n){
add(i,n+1,w[i]);
add(n+1,i,w[i]);
}
sort(e+1,e+1+idx,cmp);
foreach(i,idx){
int fx = find(e[i].u);
int fy = find(e[i].v);
if(fx!=fy){ //合并操作
f[fx] = fy;
ans += e[i].w;
}
else continue;
}
AC;
finish;
}