文章目录
- 最小生成树拓展应用
- 理论基础
- 题单
- 1. [新的开始](https://www.acwing.com/problem/content/1148/)
- 2. [北极通讯网络](https://www.acwing.com/problem/content/1147/)
- 3. [走廊泼水节](https://www.acwing.com/problem/content/348/)
- 4. [秘密的牛奶运输](https://www.acwing.com/problem/content/1150/)
最小生成树拓展应用
- 虚拟源点
- kruskal拓展
- 次小生成树
理论基础
- 任意一棵最小生成树一定可以包含无向图中权值最小的边
- 给定一张无向图 G = ( V , E ) , n = ∣ V ∣ , m = ∣ E ∣ G=(V,E),n=|V|,m=|E| G=(V,E),n=∣V∣,m=∣E∣,从E中选出k<n-1条边构成G的加一个生成森林。若再从剩余的m-k条边中选n-1-k条边添加到生成森林中,使其成为G的生成树,并且选出的边的权值之和最小。则该生成树一定可以包含m-k条边中连接生成森林的两个不连通接节点的权值最小的边
题单
1. 新的开始
第一眼:
- 要么和其他矿井建立电网共用一个发电站,要么自建发电站
- 只有当自建站比建电网费用要小时,才自建站
- 把自环也当成一条边放进去sort,当用不到点时就结束?
- 已经有电力供应的也可以给其他供应,意思就是进入了生成树就具有供电能力,因此不用担心加进来的边是那个点拉进来的
思考:
感觉是可以prim算法的
- 为什么prim要过还得有 g [ j ] [ i ] = m i n ( g [ j ] [ i ] , v ) g[j][i]=min(g[j][i],v) g[j][i]=min(g[j][i],v),一篇ac题解
小试牛刀,没过
听y话:
建立一个超级源点,可以解决从哪个点开始的问题,如果只选最小点开始,会把其他自环(也应当看成一条边)忽略而没考虑到
#include<bits/stdc++.h>using namespace std;
const int N=310,INF=0x3f3f3f3f;
int g[N][N],v[N],d[N],st[N];
int res,n;void prim(int s){memset(d,0x3f,sizeof d);d[s]=g[s][s];for(int i=1;i<=n;i++){int t=-1;for(int j=1;j<=n;j++){if(!st[j]&&(t==-1||d[t]>d[j])){t=j;}}st[t]=1;//cout<<t<<","<<d[t]<<' ';res+=d[t];for(int j=1;j<=n;j++) d[j]=min(d[j],g[t][j]);}
}signed main(){cin>>n;//memset(g,0x3f,sizeof g);int minx=INF,mindex=0;for(int i=1;i<=n;i++){cin>>g[i][i];for(int j=1;j<=n;j++){g[j][i]=min(g[j][i],g[i][i]);}if(minx>g[i][i]){mindex=i;minx=g[i][i];}}for(int i=1;i<=n;i++){for(int j=1;j<=n;j++){int x;cin>>x;if(i!=j) g[i][j]=x;}}prim(mindex);//cout<<endl;cout<<res<<endl;return 0;
}
手搓建立一个超级源点,ac了,prim算法
#include<bits/stdc++.h>using namespace std;
int n;
const int N=310;
int g[N][N],st[N],d[N];
int res;int prim(){memset(d,0x3f,sizeof d);d[0]=0;for(int i=0;i<=n;i++){int t=-1;for(int j=0;j<=n;j++){if(!st[j]&&(t==-1||d[t]>d[j])){t=j;}}st[t]=1;res+=d[t];for(int j=0;j<=n;j++) d[j]=min(d[j],g[t][j]);}
}signed main(){cin>>n;for(int i=1;i<=n;i++){int x;cin>>x;g[0][i]=g[i][0]=x;}for(int i=1;i<=n;i++){for(int j=1;j<=n;j++){cin>>g[i][j];}}prim();cout<<res<<endl;return 0;
}
2. 北极通讯网络
第一眼:
- 加了k限制的最小生成树,能不能用一个变量去计数呢?
- 挺复杂的一道题,看的第一遍没懂
听y讲:
- 涉及到通信问题——”中转“,卫星通信(有限),无线收发器(无限)
- 用并查集就不需要二分了
思考:
- 要找到最小的d,那一定是先让小边进入kruskal
- 关于连通块的问题
- 并查集
- bfs和dfs有联想到
- 有点明白,因为这道题一开始并没有直接相连的点,所有点都是独立的,我们要去找最小生成树的话,在本题用kruskal的时候,枚举一次,连通块的个数就会减一
- 找到第一个能让剩余连通块个数小于等于k的边就行了
版本一过啦,烙铁~
#include<bits/stdc++.h>using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
const int N=510,M=N*N;
PII dian[N];
int fa[N];
int n,k;double get_dis(PII a,PII b){double dx=a.x-b.x;double dy=a.y-b.y;return sqrt(dx*dx+dy*dy);
}struct edge{int x,y;double z;bool operator<(const edge& M)const{return z<M.z;}
}e[M];int find(int x){if(x!=fa[x]) fa[x]=find(fa[x]);return fa[x];
}signed main(){cin>>n>>k;for(int i=1;i<=n;i++) fa[i]=i;for(int i=1;i<=n;i++){int x,y;cin>>x>>y;dian[i]={x,y};}int cnt=0;for(int i=1;i<=n;i++){for(int j=1;j<=n;j++){double dist=get_dis(dian[i],dian[j]);e[cnt++]={i,j,dist};}}sort(e,e+cnt);int count=n;double res=0;for(int i=0;i<cnt;i++){int a=e[i].x,b=e[i].y;double c=e[i].z;a=find(a),b=find(b);if(a!=b){fa[a]=b;res=c;count--;}if(count<=k){break;}}printf("%.2f\n",res);return 0;
}
3. 走廊泼水节
第一眼:
- 何为完全图(俩俩之间有边就是完全图)
- 求的是增加的,而不总的,好像可以kruskal解决,但是得知道完全图是什么意思
听y说:
- 按照什么样的顺序连接能够得到最小值
- 新边 < w i w_i wi ❌——》不满足生成树定义
- 新边 = w i w_i wi ❌——》要求生成树唯一
思考:
- 关于 新边 〉= w i + 1 w_i+1 wi+1 为什么构造的生成树一定是唯一的
- 因为你要求最小生成树,如果 w i + 1 w_i+1 wi+1 不唯一,那就意味着有 w i w_i wi可以被添加到里面去,那求的原来的生成树就不是最小生成树了,和原树是一个最小生成树矛盾。
- 怎么把两个集合的所有点连起来
- 用并查集维护各连通块点的个数
- 优质题解
#include<bits/stdc++.h>using namespace std;
int n;
const int N=6e3+10,M=N*N;
int fa[N],psize[N];struct edge{int x,y,z;bool operator<(const edge& M)const{return z<M.z;}
}edges[M];int find(int x){if(x!=fa[x]) fa[x]=find(fa[x]);return fa[x];
}signed main(){int t;cin>>t;while(t--){cin>>n;for(int i=1;i<=n;i++) fa[i]=i,psize[i]=1;for(int i=0;i<n-1;i++){int a,b,c;cin>>a>>b>>c;edges[i]={a,b,c};}sort(edges,edges+n-1);int res=0;for(int i=0;i<n-1;i++){int a=find(edges[i].x),b=find(edges[i].y),c=edges[i].z;if(a!=b){res+=((psize[a]*psize[b]-1)*(c+1));//因为是还需要多少边,所以原本存在的c不用加psize[b]+=psize[a];fa[a]=b;}}cout<<res<<endl;}return 0;
}
4. 秘密的牛奶运输
第一眼:
- 又是奶牛,又是usaco
- 费用第二小怎么搞次最小生成树
- 费用第二严格大于费用最小,距离z代表着成本
思考:
可不可以找到最小生成树的后一条边(这个边需要满足能生成树)当作答案?
啥玩意
听y说:
- 注意总长度会爆int,要开long long
#include<bits/stdc++.h>#define int long long
using namespace std;
int n,m;
const int N=510, M=1e4+10;
int fa[N],d1[N][N],d2[N][N];
//d1存储的是两点之间路径的最长的边
//d2存储的是两点之间路径的次长的边
int h[N],e[2*N],ne[2*N],w[2*N],idx;
//因为是树的结构,可以看成每个点最多有两个子节点void add(int a,int b,int c){e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}struct edge{int x,y,z;bool f;bool operator<(const edge& M)const{return z<M.z;}
}es[M];int find(int x){if(x!=fa[x]) fa[x]=find(fa[x]);return fa[x];
}//d1,d2形式参数的类型是一维数组,而实参穿的也是一维数组的地址
void dfs(int u,int father,int dmax1,int dmax2,int d1[],int d2[]){d1[u]=dmax1,d2[u]=dmax2;for(int i=h[u];~i;i=ne[i]){int j=e[i];if(j!=father){int td1=dmax1,td2=dmax2;if(w[i]>td1) td2=td1,td1=w[i];else if(w[i]<td1&&w[i]>td2) td2=w[i];dfs(j,u,td1,td2,d1,d2);}}
}signed main(){cin>>n>>m;memset(h,-1,sizeof h);//memset(d1,0x3f,sizeof d1);//memset(d2,0x3f,sizeof d2);for(int i=1;i<=n;i++) fa[i]=i;for(int i=0;i<m;i++){int x,y,z;cin>>x>>y>>z;es[i]={x,y,z};}sort(es,es+m);//求最小生成树int sum=0;for(int i=0;i<m;i++){int a=es[i].x,b=es[i].y,c=es[i].z;int pa=find(a),pb=find(b);if(pa!=pb){fa[pa]=pb; //这里需要找到各自的父节点然后再创建连接sum+=c;add(a,b,c),add(b,a,c);es[i].f=true;}}//以每个点为根找到其与其他点之间的最远距离?for(int i=1;i<=n;i++) dfs(i,-1,-1e9,-1e9,d1[i],d2[i]);//debug dfs找任意两点之间路径最长边和次长边//for(int i=1;i<=n;i++){// cout<<i<<":"<<endl;// int k=h[i];// for(int j=k;~j;j=ne[j]){// cout<<e[j]<<","<<d1[i][e[j]]<<","<<d1[i][e[j]]<<" ";// }// cout<<endl;//}//为什么debug代码打完就过了?int res=1e18;for(int i=0;i<m;i++){bool f=es[i].f;int a=es[i].x,b=es[i].y,c=es[i].z;if(!f){if(c>d1[a][b])res=min(res,sum+c-d1[a][b]);else if(c>d2[a][b]){res=min(res,sum+c-d2[a][b]);}}}cout<<res<<endl;return 0;
}
后台测试样例
4 4
1 2 1
2 3 2
3 4 1
2 4 25