最小生成树刷题汇总
Acwing 346 走廊泼水节
给定一棵 N 个节点的树,要求增加若干条边,把这棵树扩充为完全图,并满足图的唯一最小生成树仍然是这棵树。
求增加的边的权值总和最小是多少。
注意: 树中的所有边权均为整数,且新加的所有边权也必须为整数。
输入格式
第一行包含整数 tt,表示共有 tt 组测试数据。
对于每组测试数据,第一行包含整数 NN。
接下来 N−1N−1 行,每行三个整数 X,Y,ZX,Y,Z,表示 XX 节点与 YY 节点之间存在一条边,长度为 ZZ。
输出格式
每组数据输出一个整数,表示权值总和最小值。
每个结果占一行。
数据范围
1≤N≤60001≤N≤6000
1≤Z≤1001≤Z≤100
输入样例:
2
3
1 2 2
1 3 3
4
1 2 3
2 3 4
3 4 5
输出样例:
4
17
题目分析
我们按照Kruscal的流程,一步步来连接生成树的的点。当我们把两个集合相连时。为了保证最小生成树不变。还能构成完全图。我们需要给统计所需要的道路,
可以直接用(v+1)*(size[x] *size[y]-1)来计算,其中v是当前插入边的权值,x,y是两个集合。size是集合的元素个数。在维护并查集过程中一起维护即可
代码
//难度 *** (考察最小生成树概念的理解)
#include<bits/stdc++.h>
using namespace std;
struct node{
int u,v,w;
bool operator<(const node a)const{
return w<a.w;
}
}e[20010];
int fa[20010],s[20010];
int find(int x) // 并查集
{
if (fa[x] != x) fa[x] = find(fa[x]);
return fa[x];
}
int main()
{
int t,n;
cin>>t;
while(t--)
{
cin>>n;
for(int i=1;i<=n-1;i++)
scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
sort(e+1,e+n);
for(int i=1;i<=n;i++)
fa[i]=i,s[i]=1;
int ans=0;
for(int i=1;i<=n-1;i++)
{
int x=find(e[i].u);
int y=find(e[i].v);
if(x!=y)
{
fa[x]=y;
ans+=(e[i].w+1)*(s[x]*s[y]-1);
s[y]+=s[x];
}
}
cout<<ans<<endl;
}
return 0;
}
POJ1639
在一张n个点的无向联通图中,求1号节点(Park)最大为k度时的最小生成树。(节点编号为字符串)
输入格式
第一行包含整数 n,表示人和人之间或人和公园之间的道路的总数量。
接下来 n 行,每行包含两个字符串 A、B 和一个整数 L,用以描述人 AA 和人 BB 之前存在道路,路长为 L,或者描述某人和公园之间存在道路,路长为 L。
道路都是双向的,并且人数不超过 20,表示人的名字的字符串长度不超过 10,公园用 Park 表示。
再接下来一行,包含整数 s,表示公园的最大停车数量。
你可以假设每个人的家都有一条通往公园的道路。
输出格式
输出 Total miles driven: xxx,其中 xxx 表示所有汽车行驶的总路程。
输入样例:
10
Alphonzo Bernardo 32
Alphonzo Park 57
Alphonzo Eduardo 43
Bernardo Park 19
Bernardo Clemenzi 82
Clemenzi Park 65
Clemenzi Herb 90
Clemenzi Eduardo 109
Park Herb 24
Herb Eduardo 79
3
输出样例:
Total miles driven: 183
题目解析
建图稍微处理一下。限制了一号结点最大度数后。我们先正常连最小生成树。统计一号结点度数。按顺序删除结点。找到一个删去边在加其他边的更快的方案。这个过程实现可以参考代码注释。代码有点长。但是逻辑不难理解。代码也写的比较结构化。
#include<bits/stdc++.h>
using namespace std;
int n, m, s, deg, ans;
int a[32][32], d[32], conm[32],mst[32][32];//conm数组存每个点最后一次被更新时所连的点
bool v[32];
void read() {
map<string, int> h;
cin >> m;
h["Park"] = 1; n = 1;
memset(a, 0x3f, sizeof(a)); // 原图对应的邻接矩阵
for (int i = 1; i <= m; i++) {
int x, y, z;
char sx[12], sy[12];
scanf("%s%s%d", sx, sy, &z);
if (!h[sx]) h[sx] = ++n;
if (!h[sy]) h[sy] = ++n;
x = h[sx], y = h[sy];
a[x][y] = min(a[x][y], z);
a[y][x] = min(a[y][x], z);
}
cin >> s;
}
void prim() {
memset(d, 0x3f, sizeof(d));
memset(v, 0, sizeof(v));
d[1] = 0;
for (int i = 1; i < n; i++) {
int x = 0;
for (int j = 1; j <= n; j++)
if(!v[j]&&(x==0||d[x]>d[j]))x=j;
v[x]=true;
for(int j=1;j<=n;j++)
{
if(!v[j]&&d[j]>a[x][j])
d[j]=a[x][j],conm[j]=x;
}
}
memset(mst,0x3f,sizeof(mst));
for(int j=1;j<=n;j++)
{
ans+=d[j];//统计最小生成树大小
if(conm[j]==1)deg++;//统计1的入度
mst[j][conm[j]]=mst[conm[j]][j]=d[j];//建最小生成数的图。
}
}
void dfs(int now)//求出他所在的连通块
{
for(int i=1;i<=n;i++)
{
if(mst[now][i]!=0x3f3f3f3f&&!v[i])
v[i] = true,dfs(i);
}
}
int find_min(int &x,int &y)
{
int min_edge=1<<30;
for(int i=2;i<=n;i++)if(v[i])//第一个边只能在第一部分选
for(int j=2;j<=n;j++)if(!v[j])//第二个边只能在第二部分选
if(a[i][j]<min_edge)min_edge=a[i][j],x=i,y=j;
return min_edge;
}
void solve()//选择一个最优的方案,删一条1出去的边,然后在把联通块相连。优先需要付出代价最小的方案。
{
int mini,minx,miny,min_val=1<<30;
for(int i=2;i<=n;i++)
{
if(mst[1][i]==0x3f3f3f3f)continue;
memset(v,0,sizeof(v));
v[1]=true;
v[i]=true;
dfs(i);
int x, y;
int add_edge=find_min(x,y);//x和y是把两部分连通块连起来所需付出的最小代价。
if(add_edge<0x3f3f3f3f&&add_edge-mst[1][i]<min_val)//记录所有可加边权值-删去边的权值的值得最小值。
{
min_val=add_edge-mst[1][i];
minx=x,miny=y,mini=i;
}
}
//删边连边
ans+=min_val;
mst[1][mini]=mst[mini][1]=0x3f3f3f3f;
mst[minx][miny]=mst[miny][minx]=a[minx][miny];
}
int main() {
read();
prim();
while (deg > s)//一直删,直到满足题目要求
{
solve();
deg--;
}
cout <<"Total miles driven: "<<ans << endl;
}
POJ 2728 最优比例生成树
这也是一个经典题了。实质上是一个01分数规划问题,01规划问题目标是让s最大。

其实观察这个式子,容易发现。不管可以根据内部的值大于0或小于0作为二分的依据。找到最优的s。在这道题中。我们只需要不断二分来做最小生成树就行了。本质上没有区别。由于数据范围。只能用Prim写。细节有注释
//难度 **** 最小生成树+01分数规划 ,具体推式子改天有空写个题解?也可能不写
#include<bits/stdc++.h>
using namespace std;
const double eps=1e-8;
const int N=1010;
int n,w[N],v[N];
double d[N];
struct node{
int x,y;
}a[N];
double get(node a,node b)//不能用hypot 很慢
{
double dx=a.x-b.x;
double dy=a.y-b.y;
return sqrt(dx*dx+dy*dy);
}
bool check(double mid)
{
memset(v,0,sizeof(v));
memset(d,0x42,sizeof(d));
d[1]=0;
double tmp=0;
for(int i=1;i<=n;i++)
{
int t=-1;
for(int j=1;j<=n;j++)
if(!v[j]&&(t==-1||d[j]<d[t]))t=j;
v[t]=1;
tmp+=d[t];
for(int j=1;j<=n;j++)
if(!v[j]&&d[j]>(fabs(w[t]-w[j])-mid*get(a[t],a[j])))
d[j]=fabs(w[t]-w[j])-mid*get(a[t],a[j]);
}
return tmp>=0;
}
int main()
{
while(cin>>n,n)
{
for(int i=1;i<=n;i++)cin>>a[i].x>>a[i].y>>w[i];
double l=0,r=1e6;
while(r-l>eps)
{
double mid=(l+r)/2;//double 不能用<<
if(check(mid))l=mid;
else r=mid;
}
printf("%.3lf\n",l);
}
}
POJ 2031
rbbb有N个魔法球,每个魔法球的中心位于三维空间的(x,y,z)坐标,假定每个魔法球的魔法力场为以其中心为圆心的半径为r的球体,若两个球体之间相交相切或相包含,则两个魔法球可以直接相连,否则需要在两个魔法球之间需要通过魔力连接,连接两个魔法球消耗的法力值为两个魔法球中心相连的距离减去两个半径,现在需要使任意两个魔法球都能直接或间接相连,求所需的最少法力值。 N<=100,坐标非负且小于100.000。
input
多组输入,第一行为N,接下来N行每行四个数字,分别表示第i个魔法球中心坐标x,y,z和魔法力场范围半径r,以N=0结束。
Output
所需的最少法力值
Sample Input
5
10.000 10.000 10.000 10.000
20.000 20.000 20.000 20.000
30.000 30.000 30.000 30.000
40.000 40.000 40.000 40.000
50.000 50.000 50.000 50.000
3
12.345 23.456 34.567 1.234
1.234 2.345 3.456 2.345
4.321 5.432 6.543 3.456
0
Sample Output
0.000
29.582
题目解析
没啥难度。就建个图就完了
#include <iostream>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <string>
#include <map>
#include <iomanip>
#include <algorithm>
#include <queue>
#include <stack>
#include <set>
#include <vector>
using namespace std;
double x[110],y[110],z[110],r[110],ans;
int fa[110],cnt;
struct e{
int x,y;
double z;
bool operator <(const e x)const{
return z<x.z;
}
}edge[10010];
int find(int x)
{
if(fa[x]==x)return x;
else return find(fa[x]);
}
double dis(int a,int b)
{
double tmp=(x[a]-x[b])*(x[a]-x[b])+(y[a]-y[b])*(y[a]-y[b])+(z[a]-z[b])*(z[a]-z[b]);
tmp=sqrt(tmp);
if(tmp<=r[a]+r[b])return 0;
else return tmp-r[a]-r[b];
}
int main()
{
int n;
while(scanf("%d",&n))
{
if(n==0) break;
cnt=0,ans=0;
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=1;i<=n;i++) cin>>x[i]>>y[i]>>z[i]>>r[i];
for(int i=1;i<n;i++)
{
for(int j=i+1;j<=n;j++)
{
if(dis(i,j)==0)
{
int fx=find(i);
int fy=find(j);
if(fx!=fy) fa[fy]=fx;
}
else
{
edge[++cnt].x=i;
edge[cnt].y=j;
edge[cnt].z=dis(i,j);
}
}
}
sort (edge+1,edge+1+cnt);
for(int i=1;i<=cnt;i++)
{
int fx=find(edge[i].x);
int fy=find(edge[i].y);
if(fx==fy) continue;
fa[fy]=fx;
ans+=edge[i].z;
}
cout<<fixed<<setprecision(3)<<ans<<endl;
}
return 0;
}
POJ 2377
谷仓之间有一些路径长度,然后要在这些谷仓之间建立一些互联网,花费的成本与长度成正比,,并且要使这些边连起来看的像一课“树”,然后使成本最大
Input
* Line 1: Two space-separated integers: N and M
* Lines 2..M+1: Each line contains three space-separated integers A, B, and C that describe a connection route between barns A and B of cost C.
Output
* Line 1: A single integer, containing the price of the most expensive tree connecting all the barns. If it is not possible to connect all the barns, output -1.
Sample Input
5 8
1 2 3
1 3 7
2 3 10
2 4 4
2 5 8
3 4 6
3 5 2
4 5 17
Sample Output
42
题目解析
就是求最大生成树。这里是为了写链式前向星。所以写的堆优化prim,正常prim和kru都可以做
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <vector>
#include <map>
#include <queue>
#include <algorithm>
#include <math.h>
#include <cstdio>
using namespace std;
const int maxn=1010;
const int maxm=40010;
int head[maxn],ans,n,m,num,cnt,vis[maxn],dis[maxn];
struct node{
int to,val,nex;
}e[maxm];
typedef pair <int,int> pii;//第一项是边权,第二项是点
priority_queue<pii>q;
void addedge(int u,int v,int w)
{
head[0]++;
e[head[0]].nex=head[u];
head[u]=head[0];
e[head[0]].to=v;
e[head[0]].val=w;
}
void Prim()
{
q.push(make_pair(0,1));
while(!q.empty() && num < n)
{
int d = q.top().first;
int u = q.top().second;
q.pop();
if(vis[u]) continue;
ans += d;
num++;
vis[u] = 1;
for(int i=head[u];i;i=e[i].nex)
if(e[i].val > dis[e[i].to]){
dis[e[i].to] = e[i].val;
q.push(make_pair(e[i].val,e[i].to));
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<m;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
addedge(u,v,w);
addedge(v,u,w);
}
Prim();
if(n==num)printf("%d\n",ans);
else printf("-1\n");
return 0;
}
计蒜客 - T2044 奶酪
现有一块大奶酪,它的高度为 hh,它的长度和宽度我们可以认为是无限大的,奶酪中间有许多 半径相同 的球形空洞。我们可以在这块奶酪中建立空间坐标系,在坐标系中,奶酪的下表面为 z=0z=0,奶酪的上表面为 z=hz=h。
现在,奶酪的下表面有一只小老鼠 Jerry,它知道奶酪中所有空洞的球心所在的坐标。如果两个空洞相切或是相交,则 Jerry 可以从其中一个空洞跑到另一个空洞,特别地,如果一个空洞与下表面相切或是相交,Jerry 则可以从奶酪下表面跑进空洞;如果一个空洞与上表面相切或是相交,Jerry 则可以从空洞跑到奶酪上表面。
位于奶酪下表面的 Jerry 想知道,在不破坏奶酪的情况下,能否利用已有的空洞跑到奶酪的上表面去?
空间内两点 P1(x,y,z)P1(x,y,z),P2(x,y,z)P2(x,y,z) 的距离公式如下:dist(P1,P2)=√(x1−x2)2+(y1−y2)2+(z1−z2)2dist(P1,P2)=(x1−x2)2+(y1−y2)2+(z1−z2)2
输入格式
输入文件的第一行,包含一个正整数 TT,代表该输入文件中所含的数据组数。
接下来是 TT 组数据,每组数据的格式如下:
第一行包含三个正整数 n,h,rn,h,r,两个数之间以一个空格分开,分别代表奶酪中空洞的数量,奶酪的高度和空洞的半径。
接下来的 nn 行,每行包含三个整数 x,y,zx,y,z,两个数之间以一个空格分开,表示空洞球心坐标为(x,y,z)(x,y,z)。
输出格式
输出文件包含 TT 行,分别对应 TT 组数据的答案,如果在第 ii 组数据中,Jerry 能从下表面跑到上表面,则输出 "Yes",如果不能,则输出"No"(均不包含引号)。
题目解析
2017NOIP的题目。其实就是判断图的联通。并查集维护一下就行。分别统计所有上边界的点和下边界的点。最后找是否有两个点在同一个集合中即可。
#include<bits/stdc++.h>
using namespace std;
int fa[1010];
long long int x[1010],y[1010],z[1010],di[1010],gao[1010];
int find(int x)
{
if(fa[x]==x)return x;
else find(fa[x]);
}
double dis(int a,int b)
{
double tmp=(x[a]-x[b])*(x[a]-x[b])+(y[a]-y[b])*(y[a]-y[b])+(z[a]-z[b])*(z[a]-z[b]);
tmp=sqrt(tmp);
return tmp;
}
int main()
{
int t;
long long n,h,r,q,p;
cin>>t;
while(t--)
{
cin>>n>>h>>r;
q=1,p=1;int flag=0;
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=1;i<=n;i++)
{
cin>>x[i]>>y[i]>>z[i];
if(z[i]<=r)di[q++]=i;
if(z[i]+r>=h)gao[p++]=i;
for(int j=1;j<=i;j++)
{
if(dis(i,j)<=2*r)
{
int fx=find(i);
int fy=find(j);
if(fx==fy)continue;
fa[fy]=fx;
}
}
}
for(int i=1;i<q;i++)
for(int j=1;j<p;j++)
if(find(di[i])==find(gao[j])){
flag=1;
break;
}
if(flag)cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
}
HDU - 4786 Fibonacci Tree
月读准备去几个城市旅游,他在网站上了解到,这几个城市有ni个景点,mi条连接景点之间的街道(ni<=mi-1),所有景点都可以通过街道到达相互,月读打算去参观每个城市的所有的景点,而且鉴于月读记性不好,他只想记住mi条街道中尽量少的街道,并且只通过记下的街道在景点间移动,计算出要记下哪些街道对月读轻而易举(这就是一个生成树问题,月读表示无压力),但是不幸的是,每条街道上可能有贩卖该街道的街道纪念品,月读一旦经过有纪念品的街道就会购买该街道纪念品(当然,多次经过也总共只买一个纪念品),因为不可告人的原因,月读在每个城市只想买k种街道纪念品,k为任意斐波那契数列中的数值(k=1,2,3,5,8……),现在月读想请你帮他计算,是否有一种记忆街道的可能,使得月读购买的纪念品数量为斐波那契数列中的任意值。
Input
第一行输入一个数值T,代表月读接下来要去几个城市
对于每个城市,第一行为两个整数N(1 <= N <= 10^5) ,M(0 <= M <= 10^5)
接下来m行,每行有两个数值u,v(1 <= u,v <= N, u!= v)与c,uv代表街道两侧的景点编号,c代表该街道上是否有街道纪念品
Output
对每个城市,输出一行Case #x:s,x是城市的编号,s为Yes或No,表示了是否有该种可能
题目解析
2013成都区域赛的一个题。对于边权为1的图。最小生成树和最大生成树之间所有值都能出现。所以只需要分别求出最大生成树和最小生成树。判断其中有没有fib数即可。需要注意的是。要先判图是否联通。否则会TLE
#include <cstdio>
#include <algorithm>
using namespace std;
struct Edge {
int s,e,c;
Edge(int ss=0,int ee=0,int cc=0):s(ss),e(ee),c(cc) {}
bool operator < (const Edge& a) const {
return c<a.c;
}
}g[100005];
int fa[100005],fa,fb,f[25];
int getfar(int a) {
if(fa[a]!=a)
fa[a]=getfar(fa[a]);
return fa[a];
}
void Merge(int a,int b) {
fa=getfar(a),fb=getfar(b);
if(fa!=fb)
fa[fb]=fa;
}
int main() {
int n,m,i,T,kase=0,cnt,low,hig;
bool flag;
i=1,f[0]=1,f[1]=2;
while(f[i]<100005) {
f[i+1]=f[i]+f[i-1];
++i;
}
scanf("%d",&T);
while(kase<T) {
scanf("%d%d",&n,&m);
low=hig=0,cnt=1;
for(i=0;i<m;++i)
scanf("%d%d%d",&g[i].s,&g[i].e,&g[i].c);
for(i=1;i<=n;++i)
fa[i]=i;
sort(g,g+m);
for(i=0;i<m&&cnt!=n;++i) {
if(getfar(g[i].s)!=getfar(g[i].e)) {
Merge(g[i].s,g[i].e);
++cnt;
low+=g[i].c;
}
}
flag=false;
if(cnt==n) {//如果图是连通的
cnt=1;
for(i=1;i<=n;++i)
fa[i]=i;
for(i=m-1;i>=0&&cnt!=n;--i) {
if(getfar(g[i].s)!=getfar(g[i].e)) {
Merge(g[i].s,g[i].e);
++cnt;
hig+=g[i].c;
}
}
i=0;
while(f[i]<low)
++i;
if(f[i]<=hig)
flag=true;
}
printf("Case #%d: %s\n",++kase,flag?"Yes":"No");
}
return 0;
}
POJ 1679
从前有两只猫。它们实在不知道该出什么题了。
于是它们放弃了治疗,开始玩一个游戏:从乡镇地图中已有的道路里面删除一些道路,并且删除完毕后图仍然是连通的。在所有方案中,删除道路总长度最大的方案为最优方案。
两只猫同时完成了这个游戏。它们都坚信自己是赢家。已知它们的完成方式不同,请判断有没有可能它们的实现方案都是最优的。
Input
第一行是一个整数 t (1 <= t <= 20), 测试用例的数量。每个用例代表一张图,第一行是n和m (1 <= n <= 100), 分别为城镇数和道路数。接下来m行为m个三元组 (xi, yi, wi),表示编号为xi和yi的城镇被长度为wi的道路连接。两个城镇之间最多被一条道路连接。
Output
对于每个用例,如果答案为否定(即不可能都是最优方案),输出最优方案剩余的(注意不是删除的)道路总长度。否则输出字符串 'Not Unique!'(不带引号)。
Sample Input
2
3 3
1 2 1
2 3 2
3 1 3
4 4
1 2 2
2 3 2
3 4 2
4 1 2
Sample Output
3
Not Unique!
题目解析
次小生成树的板子。
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <cstring>
using namespace std;
#define maxn 111
const int inf =0x3f3f3f3f;
int maxx[maxn][maxn];
int mp[maxn][maxn];
int vis[maxn];
int dis[maxn];
bool connect[maxn][maxn];
int father[maxn];
int n,m;
int prim()
{
memset(vis,false,sizeof(vis));
for(int i=1;i<=n;i++)
{
dis[i]=mp[1][i];
father[i]=1;///
}
vis[1]=1;
dis[1]=0;
int res=0;
for(int i=1;i<=n;i++)
{
int index=-1,minn=inf;
for(int j=1;j<=n;j++)
{
if(vis[j]==0&&dis[j]<minn)
{
index=j;
minn=dis[j];
}
}
if(index==-1)
{
return res;
}
int pre=father[index];/// 取到这次加入点的上一次松弛或者连接的点
vis[index]=1;
res+=minn;
connect[pre][index]=false;///最小生成树用的边,把他们的联系去掉
connect[index][pre]=false;///
maxx[pre][index]=minn;///pre ,index两点间需要的权值(直接相连,直接赋值)
for(int j=1;j<=n;j++)///
maxx[j][index]=max( maxx[pre][index],maxx[j][pre] );///(间接相连,屡次更新)
for(int j=1;j<=n;j++)
{
if(vis[j]==0&&dis[j]>mp[index][j])
{
dis[j]=mp[index][j];
father[j]=index;///
}
}
}
return res;
}
int main()
{
int T;
cin>>T;
while(T--)
{
memset(mp,inf,sizeof(mp));
memset(connect,false,sizeof(connect));
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int x,y,d;
scanf("%d%d%d",&x,&y,&d);
mp[x][y]=d;
mp[y][x]=d;
connect[x][y]=true;///
connect[y][x]=true;///
}
int ans=prim();
int over=0;///下面代码,如果某条有关系的边未被最小生成树使用,而且效果相同,就表示有次小生成树的存在
for(int i=1;!over&&i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(connect[i][j]==false||mp[i][j]==inf)
continue;
if(mp[i][j]==maxx[i][j])
{
over=1;
break;
}
}
}
if(over)
printf("Not Unique!\n");
else
printf("%d\n",ans);
}
return 0;
}

浙公网安备 33010602011771号