最小生成树专题
前言
因为老师满含微笑的喊我写所以我就写了
嗯嗯正式接触图论这一板块了,感觉不写点什么,有些对不起自己,所以就有了你们所看到的这篇博客
没有什么多的废话,赶紧赶紧地进入我们的博客里面来吧【很急诶→_→】!
第一题!-最优布线问题-为什么不用WIFI
题目描述
学校有n台计算机,为了方便数据传输,现要将它们用数据线连接起来。两台计算机被连接是指它们中间有数据线连接。由于计算机所处的位置不同,因此不同的两台计算机的连接费用往往是不同的。 当然,如果将任意两台计算机都用数据线连接,费用将是相当庞大的。
为了节省费用,我们采用数据的间接传输手段,即一台计算机可以间接的通过若干台计算机(作为中转)来实现与另一台计算机的连接。 现在由你负责连接这些计算机,你的任务是使任意两台计算机都连通(不管是直接的或间接的)。
输入
第1行:1个整数n(2≤n≤100),表示计算机的数目。
此后的n行,每行n个整数。第x+1行y列的整数表示直接连接第x台计算机和第y台计算机的费用。
输出
第1行:1个整数,表示最小的连接费用
样例输入
3
0 1 2
1 0 1
2 1 0
样例输出
2
分析-得出结论-水题
输入的是一个邻接矩阵,要求任意两点连通+权值和最小。
嗯任意两点连通很明显是在暗示你往树方面想,权值和最小则是在疯狂暗示你 “最小生成树”。
于是我们得出结论:这是道模板型水题
Kruskal算法-以边选点
-简介
Kruskal的思想很简单:
你要最小和是吧?每次给你加条最小边
你要一棵树是吧?每次给你加边都无环
非常、非常经典的贪心思想
-实现
(1)将边按权值大小排序
(2)建立图集
(3)如果将这条边加入
(4)直到所有的边都枚举一遍
-代码
struct Edge
{
int u,v,value;
}G[MAXN*(MAXN-1)/2+5];
int F[MAXN+5];//并查集判断连通
int Tree[MAXN+5][MAXN+5];//邻接矩阵存树
bool cmp(Edge a,Edge b)
{
if(a.value<b.value) return 1;
else return 0;
}
int Find(int x)
{
if(F[x]==0) return x;
F[x]=Find(F[x]);
return F[x];
}
void Union(int x,int y)
{
F[Find(x)]=y;
Find(x);Find(y);
}
int main()
{
int n,len=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
Edge e;
e.u=i;e.v=j;
scanf("%d",&e.value);
if(j>i) G[++len]=e;
}
sort(G+1,G+len+1,cmp);
for(int i=1;i<=len;i++)
{
if(Find(G[i].u)!=Find(G[i].v))
{
Union(G[i].u,G[i].v);
Tree[G[i].u][G[i].v]=Tree[G[i].v][G[i].u]=G[i].value;
}
}
}
-证明
注意!以下内容过于枯燥!请谨慎地往下读
用数学归纳法证明Kruskal算法适用于n阶图【阶就是结点的个数】:
(1):
(2):假设Kruskal算法对
又:G’最小生成树T’可以用Kruskal算法得到。
所以我们现在只需证明
用反证法,如果
然后:
于是假设不成立,
由数学归纳法,Kruskal算法得证。
Prim算法-从点选边
-简介
不用判断连通性问题=没有并查集
任意点作为根,一步步选离得最近的点,直到把所有点收入囊中。
依然是贪心的思想
-实现
(1)选择任意点作为Root
(2)建立图集
(3)加入边
(4)直到
-代码
int G[105][105],Tree[105][105];
int Min[105],Pre[105];
...
Min[1]=INF;
for(int i=1;i<=n;i++)
if(Min[i]!=INF)
{
Min[i]=G[1][i];
pre[i]=1;
}
for(int i=1;i<n;i++)
{
int m=1;
for(int j=1;j<=n;j++)
if(Min[m]>Min[j]) m=j;
Tree[m][pre[m]]=Tree[pre[m]][m]=Min[m];
Min[m]=INF;
for(int j=1;j<=n;j++)
if(Min[j]!=INF&&G[j][m]<Min[j])
{
Min[j]=G[j][m];
pre[j]=m;
}
}
-证明
注意!以下内容过于……
好吧我知道你们会自觉跳过的
依然反证法:假设prim生成的不是最小生成树
1).设prim生成的树为
2).假设存在
3).将
4).这与prim每次生成最短边矛盾
5).故假设不成立,命题得证.
简单易懂。
我知道你们在等什么-代码
自己改上面的Kruskal算法或Prim算法
第二题!-灌溉牧场-没钱修井,有钱接水管
题目描述
Farmer John 决定把水引到N个牧场 N (1 <= N <= 300),牧场正好从1..N编号。FJ可以用两种方法来引水:
(1)在牧场上挖一口水井;
(2)从其它已经有水的牧场连一根水管。
在牧场i挖水井的费用为W_i (1 <= W_i <= 100,000),在牧场i和j之间连水管的费用为P_ij (1 <= P_ij <=100,000; P_ij = P_ji; P_ii=0)
请算出FJ把水引到所有牧场的最小费用。
输入
第1行: 1个整数 N
第2..N + 1行: 只有1个整数 W_i
第N+2..2N+1行:每有 N 个空格分开的整数,第j个表示 P_ij
输出
第1行: 1个整数表示引水到所有牧场的最小费用。
样例输入
4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0
样例输出
9
样例说明
一共有4个牧场。挖水井的代价分别为5,4,4,3. 在不同牧场之间连水管的代价分别是 2, 3, 和 4 。Farmer John 在第4个牧场挖一口井,然后分别从第4个牧场连到其它牧场,代价为: 3 + 2 + 2 + 2 = 9.
分析-最小生成森林???
嗯……
假设我在A处建井,B处建井,共建两处
A,B管辖的范围肯定不能重叠,否则把A拆掉一样可以实现。
在A管辖的范围内,是一片最小生成树。
在B管辖的范围内,是一片最小生成树。
得出结论:题目要求将图
所以,这就是传说中的最小生成森林咯?
森林->一棵树
假设我们令森林内的所有树的根指向一个不存在的结点,则原本的森林就变成了一棵树
化归思想。
然后就可以对这棵树做我们想做的事了。
好理解的方法
井里的水从哪儿来?不妨令地底下有一口泉,下标为0。打井即是将泉与打井的位置相连,代价就为打井的代价。
代码的实现
#include<cstdio>
#include<algorithm>
using namespace std;
struct Edge
{
int u,v,value;
}G[300*300+15];
int W[305],F[305];
int Min[305],Tree[305][305];
bool vis[305];
bool cmp(Edge a,Edge b)
{
if(a.value<b.value) return 1;
else return 0;
}
int Find(int x)
{
if( F[x] == 0 ) return x;
F[x]=Find(F[x]);
return F[x];
}
void Union(int x,int y)
{
F[Find(x)]=y;
Find(x);Find(y);
}
int main()
{
int n,len=0,Min=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&W[i]);
for(int i=1;i<=n;i++)
{
Edge e;
e.u=0;e.v=i;e.value=W[i];
G[++len]=e;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
Edge e;
e.u=i;e.v=j;
scanf("%d",&e.value);
if(j>i) G[++len]=e;
}
sort(G+1,G+len+1,cmp);
int need=0,ans=0;
for(int i=1;i<=len;i++)
{
if(Find(G[i].u)!=Find(G[i].v))
{
need++;
ans+=G[i].value;
Union(G[i].u,G[i].v);
}
if(need==n) break;
}
printf("%d\n",ans);
}
EEEENNNNDDDDDDD!
就是这样,新的一天里,也请多多关照哦(ノω<。)ノ))☆.。~