先来看看题目呀,应该看过好几遍了叭:
题目描述
发展采矿业当然首先得有矿井,小 FF 花了上次探险获得的千分之一的财富请人在岛上挖了 nn 口矿井,但他似乎忘记考虑的矿井供电问题……
为了保证电力的供应,小 FF 想到了两种办法:
- 在这一口矿井上建立一个发电站,费用为 vv(发电站的输出功率可以供给任意多个矿井)。
- 将这口矿井与另外的已经有电力供应的矿井之间建立电网,费用为 pp。
小 FF 希望身为「NewBe_One」计划首席工程师的你帮他想出一个保证所有矿井电力供应的最小花费。
输入格式
第一行一个整数 nn,表示矿井总数。
第 2\sim n+12∼n+1 行,每行一个整数,第 ii 个数 v_ivi 表示在第 ii 口矿井上建立发电站的费用。
接下来为一个 n\times nn×n 的矩阵 pp,其中 p_{i,j}pi,j 表示在第 ii 口矿井和第 jj 口矿井之间建立电网的费用(数据保证有 p_{i,j}=p_{j,i}pi,j=pj,i,且 p_{i,i}=0pi,i=0)。
输出格式
输出仅一个整数,表示让所有矿井获得充足电能的最小花费。
样例
Input Output 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\%30% 的数据:1\le n\le501≤n≤50;
对于 100\%100% 的数据:1\le n\le 300,0\le v_i, p_{i,j}\le 10^51≤n≤300,0≤vi,pi,j≤105。
题意:把发电站当作供电矿井,把需要用电的,看成用电矿井,就是求如何把供电矿井和用电矿井全部连起来最小,连成一颗最小生成树。
这题我用kruskal算法,卡在了80分,就不细讲啦,贴一下码:
都属于最小生成树问题嘛!!!
我觉得最小生成树问题的核心在于建图。
比如这题,使用kruskal算法,我们如何建图呢:
1.已经存在的电站的点,我们把它的信息存进结构体的三个元素里,kk[i].x,kk[i].y,表示点,kk[i].t表示权边。
这里为什么是n+1呢:因为存下面n*n个费用的时候,我们要使用到n了,可以将它看成第n+1个点,自己连自己的。
由于我们要把单独的点和下面的n*n的费用存进一张图,所以我们单独开了个k当作kk数组的序列,记录kk数组的总数。
讲的是非常非常仔细啦!!!
#include<bits/stdc++.h>
using namespace std;
long long int n,k,i,j,m,sum=0,a[1010101],b[1010101],f[1010101],cnt=0;
struct node{
long long int x,y,t;
}kk[10100101];
bool cmp(node a,node b){
return a.t<b.t;
}
int find(long long int x){
if(f[x]==x){
return x;
}else{
return f[x]=find(f[x]);
}
}
int main(){
cin>>n;
for(i=1;i<=n;i++){
f[i]=i;
}
for(i=1;i<=n;i++){//建图过程!!!
cin>>a[i];
kk[k].x=i;
kk[k].y=n+1;
kk[k].t=a[i];
k++;
}for(i=1;i<=n;i++){
for(j=1;j<=n;j++){
cin>>m;
kk[k].x=i;
kk[k].y=j;
kk[k].t=m;
k++;
}
}sort(kk+1,kk+k,cmp);
for(i=1;i<=k-1;i++){
long long int f1=find(kk[i].x);
long long int f2=find(kk[i].y);
if(f1!=f2){
f[f2]=f1;
sum+=kk[i].t;}
}
cout<<sum<<endl;
}
主要想讲讲prim算法,(看懵了下午,看到了一位巨巨写的很优雅很优雅的prim):
#include<bits/stdc++.h>
using namespace std;
int i,j,k,n,ans,a[310][310],d[310],vis[310];
int main(){
memset(d,0x3f,sizeof(d));//初始化成最大
a[n+1][n+1]=0,d[1]=0;
cin>>n;
for(i=1;i<=n;i++){
cin>>a[i][n+1];
a[n+1][i]=a[i][n+1];
}for(i=1;i<=n;i++){
for(j=1;j<=n;j++){
cin>>a[i][j];
if(a[i][j]==0){
a[i][j]=d[0];
}
}
}
for(i=1;i<=n+1;i++){
k=0;
for(j=1;j<=n+1;j++){
if(d[j]<d[k]&&!vis[j]){
k=j;
}
}vis[k]=1;ans+=d[k];
for(j=1;j<=n+1;j++){
if(a[k][j]<d[j]&&!vis[j]){
d[j]=a[k][j];
}
}
}cout<<ans<<endl;
}
先讲讲啥是prim算法叭:
就是,一个图,不是有一堆点吗?我们随便找一个点,将它当作起点,然后把与它相邻的点的权值都给它标上!!!并找到权值最小的点(贪心的体现!!!找到了就代表访问了,下次就不找他了)再以刚才说的,找到的那个点为起点,重复上述过程!
是不是和dijisktra算法很像!!!???
只能说,确实,理解的dijiskra的话,会更好理解它。
这个码我自己老老实实敲了三遍。
一些小问题:
假设初始全不连通。
看完就知道啦!
存图过程, ,因为这两个点是一样的,所以一起存了,且当作第n+1个点。
然后下面那个,a[i][j]==0,说明它是对角线的点,自己和自己,不连通,d[0]默认初始化是无穷大(不连通)
这个循环注意,跑的是n+1昂! 因为咱前面存了n+1的打算建立的水电站。
这个呢,就是找从一个起点开始,与它所有相邻的点里面,权值最小的那个点,这里就体现了刚开始d[1]=0的作用了,使这个if能跑起来 ,不然所有初始权值都是0x3f,就没有比较的意义了,找到最小的那个,我们就标记那个点已经找过,然后ans+=权值。
比较的权值d[j]都是当前未标记的权值中最小的那个值(由下一个for循环实现,也是贪心的体现)