题解:lougu.P1550 [USACO08OCT] Watering Hole G(图论配套精选专练)
题目:P1550 [USACO08OCT] Watering Hole G
题意建模
给定 个点的无向图,使某两个点相连会有一定的边权。现在还有在任意一个点打上一个标记,其代价是给定的。使得其他店有机会与这个点相连。求最少的代价,使得整个图联通。
算法分析
题意已经足够明朗了。任意两点有建边代价,可视为所谓的边权;在任意一个点按照题目的话说是“打一口水井”。从哪里打向哪里?这是一个很有挑战性的问题。现在已经完成了题目的一半:找到一棵最小生成树,只剩下所谓“打一口水井”即做标记的问题没有解决。但是这两个问题具有相似的性质,即做某个决策时,都存在一定的代价,既然如此,对于两点之间的权值我们选择建边来处理,依照类比思维,何不视在每个点打上标记的决策为向另一个本不存在现在人为添加的点连一条权值为做这个决策时所需要的代价呢?
思及此处,想必建模已然完成。即:建立一个虚拟源点(不妨设为0),向每一个点连一条权值为在该点做一个标记的代价,然后正常存图,接着用MST的算法解决(本题采用Kruskal)。
参考程序
//Kruskal,时间复杂度为O(m*log2m),算法瓶颈为sort带来的
#include<iostream>
#include<algorithm>
using namespace std;
const int N=3e2+5,M=1e5;
int idx,fa[N],n;
struct edge{int u,v,w;}e[M];
int cfind(int x) { return fa[x]==x?x:fa[x]=cfind(fa[x]); }
void cmerge(int x,int y) { fa[cfind(x)]=cfind(y); }
int kruskal()
{
int tot=0;
for(int i=1;i<=n;i++) fa[i]=i;
sort(e+1,e+idx+1,[](const edge &a,const edge &b) { return a.w<b.w; });
for(int i=1;i<=idx;i++)
if(cfind(e[i].u)!=cfind(e[i].v))
{
cmerge(e[i].u,e[i].v);
tot+=e[i].w;
}
return tot;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>e[++idx].w;//在每个点打水井所需的代价
e[idx].u=0,e[idx].v=i;//从0点为源点向n个点相应依次连边
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
cin>>e[++idx].w;
e[idx].u=i,e[idx].v=j;
}
cout<<kruskal()<<endl;
return 0;
}
细节研讨
- 无向边注意连两次,但是本题中特殊的、极简单的结构体数组存储十分便利,所以只需要存一次
- Kruskal的排序和并查集是底层逻辑,实现时遵循题意
总结归纳
- 问题具有几个特征相似时,考虑用类比思维;
- 建立虚拟源点时图论中很常见的思路,要学会运用。怎样去用?依上文,在具有相似的特征,(本题中都是所谓的代价,而其中一种是为我们所熟知的边权,也就是两个点之间的关系,另一种是单独一个点之间的关系。这种特殊性启发我们试图化归统一两个矛盾的问题,自然会想到再建立一个虚拟源点,这样两个问题就变得基本等价了)所以会抓住这种相似之外的“矛盾”,这往往是问题的突破口。

浙公网安备 33010602011771号