文章目录
- 最小生成树拓展应用
- 理论基础
- 题单
- 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 2
5