题面
一个 n 个点 m 条边构成的无向带权图。由一些黑点与白点构成 树现在每个白点都要与他距离最近的黑点通过最短路连接(如果有很多个,可以选 取其中任意一个),我们想要使得花费的代价最小。请问这个最小代价是多少? 注意:最后选出的边保证每个白点到黑点的距离任然是最短距离。
分析
这道题考的时候写的比较久,结果思路完全错误..看着黑白就觉得像一道叫位图的题,问的是每个黑点到白点的距离,做法是将所有白点同时扩展,但是那是个无权图啊,我抖机灵如法炮制,挂到窒息。
看到正解不是很意外,核心思想就是加了一个超级源。
因为此题的难度在于如何处理多源最短路。将超级源和每个黑点连一条权值为0的边,以超级源为起点跑spfa或者dijstra,就能等效得出白点到最近的黑点的距离了。
统计这些在最近路径上的边只需要遍历一遍,显然,满足d[u]+w==d[v]的边就在最短路径上,再将这些边抽出来,在新图上跑最小生成树。
因为无论留下的道路是哪一条,对于任意白点,只要能到达黑点,那一定是最短的,因为我们本来留下的边就是最短路径。最小生成树能保证图的连通,并更优化边集的选择。所以就是两个板套在一起,就看能不能想到了。。
注意long long
#include<bits/stdc++.h> using namespace std; #define N 100010 #define INF 0x3f3f3f3f3f3f3f3f #define ll long long ll n,m,cnt=0,cot=0,num=0,ans=0,sup=0; ll first[N],col[N],fa[N],d[N],black[N],vis[N]; struct email { ll u,v,w; ll nxt; }e[N*5],a[N*5]; queue<ll>q; inline ll find(ll x) { return fa[x]==x?x:fa[x]=find(fa[x]); } bool cmp(email a,email b) { return a.w<b.w; } inline void add(ll u ,ll v,ll w) { e[++cnt].nxt=first[u];first[u]=cnt; e[cnt].u=u;e[cnt].v=v;e[cnt].w=w; } inline void rebuild(ll u ,ll v,ll w) { a[++cot].u=u;a[cot].v=v;a[cot].w=w; } void spfa(ll x) { q.push(x);vis[x]=1;d[x]=0; while(!q.empty()) { ll u=q.front();q.pop();vis[u]=0; for(ll i=first[u];i;i=e[i].nxt) { ll v=e[i].v,w=e[i].w; if(d[v]>d[u]+w) { d[v]=d[u]+w; if(!vis[v]) { q.push(v); vis[v]=1; } } } } } void kruskal() { for(ll i=1;i<=n;i++) fa[i]=i; sort(a+1,a+1+cot,cmp); for(ll i=1;i<=cot;i++) { ll u=a[i].u,v=a[i].v,w=a[i].w; ll fax=find(u),fay=find(v); if(fax!=fay) { fa[fax]=fay; ans+=w;num++; } if(num==n-1) break; } } int main() { scanf("%lld%lld",&n,&m); for(ll i=1;i<=n;i++) { scanf("%lld",&col[i]); if(col[i])add(sup,i,0); d[i]=INF; } for(ll i=1;i<=m;i++) { ll u,v,w; scanf("%lld%lld%lld",&u,&v,&w); add(u,v,w);add(v,u,w); } spfa(sup); for(ll i=1;i<=n;i++) if(d[i]==INF) { printf("impossible "); return 0; } for(ll i=1;i<=n;i++) for(ll p=first[i];p;p=e[p].nxt) if(e[p].w+d[i]==d[e[p].v]) rebuild(e[p].u,e[p].v,e[p].w); kruskal(); printf("%lld ",ans); return 0; }