题341.最小生成树扩展-acwing-Q1148–秘密的牛奶运输
一、题目
二、题解
由于牛奶运输往销售点后可由销售点继续运输往其他地方,要你求牛奶运输往各个销售点所需的次最小花费,显然这是一个求次小生成树的问题。
方法有二:
这里我们采用第二种方法,求严格(如果是非严格则下面的操作只需要找出最大边就好,不用找次最大)的次小生成树。步骤如下:
1.先用Kruskal算法求最小生成树即权和weightsum并在过程中存下未作为最小生成树的边以及将作为最小生成树的边连边建图
2.dfs由1得到的图,求出点和点之间路径中的最大边的边权dist1[ ][ ]以及次最大边的边权dist2[ ][ ]。(结果可用lca更快地预处理出来)
3.枚举未作为最小生成树的边e,满足e.w>dist1[e.u][e.v]时替换最小生成树中e的两个顶点的路径中的最大边,求min(weightsum+e.w-dist1[e.u][e.v]),不满足则看满足e.w>dist2[e.u][e.v]时替换次最大边,求min(weightsum+e.w-dist2[e.u][e.v])
代码如下:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=550,maxm=1e4+1;
struct Edge
{
int u,v;
int w;
bool operator < (const Edge &e) const
{
return w<e.w;
}
}E[maxm];
int N,M;
int h[maxn],e[maxm],w[maxm],ne[maxm],idx;
int p[maxn];
vector<Edge> surplus;
int dist1[maxn][maxn],dist2[maxn][maxn];
void add(int a,int b,int c)
{
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int findRoot(int v)
{
if(p[v]<0)
{
return v;
}
else
{
return p[v]=findRoot(p[v]);
}
}
void unionSet(int u,int v)
{
int root1=findRoot(u),root2=findRoot(v);
if(root1==root2)
{
return;
}
if(p[root1]<p[root2])
{
p[root1]+=p[root2];
p[root2]=root1;
}
else
{
p[root2]+=p[root1];
p[root1]=root2;
}
return;
}
ll Kruskal()
{
ll weightsum=0;
memset(p,-1,sizeof p);
memset(h,-1,sizeof h);//初始化邻接表表头
for(int i=0;i<M;i++)
{
int u=E[i].u,v=E[i].v,w=E[i].w;
int root1=findRoot(u),root2=findRoot(v);
if(root1!=root2)
{
add(u,v,w),add(v,u,w);//连边建图
unionSet(u,v);
weightsum+=w;
}
else
{
surplus.push_back({u,v,w});//存入非树边
}
}
return weightsum;
}
//注意求严格次小生成树时,不能只预处理两点之间最大的树边,因为当最大树边和当前枚举的非树边长度相同时,就不能替换了,但此时却可以替换长度次大的树边。因此还需同时预处理出长度次大的树边。
void dfs(int u,int fa,int maxd1,int maxd2,int d1[],int d2[])//u:当前节点,fa:上一个节点(因为反向建边了,为避免往回遍历所以要设置一个父节点参数),maxd1:当前最大边权,maxd2:当前次最大边权,d1[]:最大边权,d2[]:次最大边权
{
d1[u]=maxd1,d2[u]=maxd2;
for(int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
if(j!=fa)//没有往回搜的话
{
int td1=maxd1,td2=maxd2;
if(w[i]>td1)//当前遍历到的边边权大于当前最大边权,更新最大为w[i],次最大为原最大
{
td2=td1,td1=w[i];
}
else if(w[i]<td1&&w[i]>td2)//w[i]==td1无需更新
{
td2=w[i];
}
dfs(j,u,td1,td2,d1,d2);
}
}
}
int main()
{
scanf("%d%d",&N,&M);
for(int i=0;i<M;i++)
{
scanf("%d%d%d",&E[i].u,&E[i].v,&E[i].w);
}
sort(E,E+M);
ll weightsum=Kruskal();
for(int i=1;i<=N;i++)//dfs求出从i点出发到的点路径中的最大边权与次最大边权
{
dfs(i,0,0,0,dist1[i],dist2[i]);
}
ll res=LONG_LONG_MAX;
for(int i=0;i<surplus.size();i++)
{
int u=surplus[i].u,v=surplus[i].v,w=surplus[i].w;
ll tmp=LONG_LONG_MAX;
if(w>dist1[u][v])//大于最大
{
tmp=weightsum+w-dist1[u][v];
}
else if(w>dist2[u][v])//否则大于次最大
{
tmp=weightsum+w-dist2[u][v];
}
res=min(res,tmp);
}
printf("%lld",res);
}