1、新的开始
题意:
一共 n n n 口矿井,为保证电力的供应有两种策略:
- 在矿井 i i i 上建立一个发电站,费用为 v i v_i vi(发电站的输出功率可以供给任意多个矿井)。
- 将这口矿井 i i i 与另外的已经有电力供应的矿井 j j j 之间建立电网,费用为 p i j p_{ij} pij。
求保证所有矿井电力供应的最小花费方案。
分析:
如果只采取第二种策略,那么保证电力供应,也就是使得所有的点直接或者间接连通。为了权值最小,那便是最小生成树。
但是现在有第一种策略,自己建站。
如何在该条件下,将此题转化成最小生成树问题呢?
可以构建一个虚拟源点,超级发电站,如果有矿井要自己建站的话,就和虚拟源点连边。
于是,便是从整个图中挑选出若干条边,使得所有点直接或间接连通,便是最小生成树问题。
Code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define endl '\n'
const int N = 200010, mod = 1e9+7;
int T, n, m;
struct node{
int x, y, w;
}a[N];
int pre[N];
bool cmp(node a, node b){
return a.w < b.w;
}
int find(int x){
if(pre[x] != x) pre[x] = find(pre[x]);
return pre[x];
}
signed main(){
Ios;
cin>>n;
for(int i=0;i<=n;i++) pre[i]=i;
int cnt=0;
for(int i=1;i<=n;i++)
{
int x;cin>>x;
a[++cnt] = {i, 0, x};
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
int x;cin>>x;
if(j>i) a[++cnt] = {i, j, x};
}
sort(a+1, a+cnt+1, cmp);
int ans=0;
for(int i=1;i<=cnt;i++)
{
int x = a[i].x, y = a[i].y, w = a[i].w;
if(find(x) != find(y)){
ans += w;
pre[find(x)] = find(y);
}
}
cout << ans;
return 0;
}
2、北极通讯网络
题意:
有 n 个村庄要实现两两村庄都可以直接或间接通讯,每座村庄的坐标用一对整数 (x,y) 表示。
现有两种通讯工具:无线电 和 卫星。
无线电数量不限,但是有距离限制;卫星数量有限,没有距离限制。
现分配给了 m 个卫星,计算出应该如何分配这 m 台卫星设备,才能使所配备的无线电的距离 d 值最小。输出最小值。
分析:
从第一句话看可能是最小生成树问题,但是怎么越往后看越不像了hh。
给出了每座村庄的坐标,所以可以求出任意两村庄直接的距离 dis。
如果要实现两个村庄的连通,要么使用使用两台卫星设备,要么用距离大于等于 dis 的无线电。
所以分配给的 m 台卫星设备可以实现 m-1 条边花费为0。
将所有的村庄连通需要 n-1 条边,那么需要配备的无线电距离 d 为这些边中的最长边。
但是现在可以免于考虑 m-1 条边,所以只需要按照 kruskal 求最小生成树的思路找到最小的 n-m 条边就行了。
Code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define PII pair<int,int>
#define fi first
#define se second
const int N = 200010, mod = 1e9+7;
int T, n, m;
PII pos[N];
struct node{
int x, y;
double w;
}a[N];
int pre[N];
bool cmp(node a, node b){
return a.w < b.w;
}
int find(int x){
if(pre[x] != x) pre[x] = find(pre[x]);
return pre[x];
}
double dis(int a, int b, int c, int d)
{
return sqrt((a-c)*(a-c) + (b-d)*(b-d));
}
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>pos[i].fi>>pos[i].se;
if(m>=n){
cout << "0.00";return 0;
}
int cnt=0;
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
a[++cnt] = {i, j, dis(pos[i].first, pos[i].second, pos[j].first, pos[j].second)};
sort(a+1, a+cnt+1, cmp);
for(int i=1;i<=n;i++) pre[i] = i;
int t = 0;
double ans = 0;
for(int i=1;i<=cnt;i++)
{
int x = a[i].x, y = a[i].y;
double w = a[i].w;
if(find(x) != find(y)){
ans = w;
pre[find(x)] = find(y);
t++;
if(t==n-1-(m-1)) break;
}
}
printf("%.2f", ans);
return 0;
}
3、走廊泼水节
题意:
给定一棵 N 个节点的树,要求增加若干条边,把这棵树扩充为完全图,并满足图的唯一最小生成树仍然是这棵树。
求增加的边的权值总和最小是多少。(边权为整数)
分析:
此题考察了 kruskal 求最小生成树的原理。
如何保证扩展后的图中,最小生成树不变?
在 kruskal 求解最小生成树问题时,每次选择最小的,两端点不在同一集合的一条边,将两个端点所在的集合合并。
这两个集合是用这个权值最小的边合并的,这个边会在最小生成树中。
但是如果出现了端点在这两个集合中,并且权值更小的边,那么这两个集合的合并就会用这个边,而不会用原来的那条边了,于是最小生成树就变了。同理,如果有权值相同的边,那么最小生成树就不唯一了。
所以,对于最小生成树的每条边来说,其应该是两个端点连接的两个集合中权值最小的一个边。
所以,从小到大遍历每条边 u,边权为w,将其两个端点所在的两个集合的任意两点连边:除去此边,还有 x*y-1 条边(x 和 y 为两个集合中元素个数)。
为了使得总权值最小,还要比该边权 w 大,那就把每条边的权值赋值为 w+1。
Code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
const int N = 200010, mod = 1e9+7;
int T, n, m;
struct node{
int x, y, w;
}a[N];
int pre[N], siz[N];
bool cmp(node a, node b){
return a.w < b.w;
}
int find(int x){
if(pre[x] != x) pre[x] = find(pre[x]);
return pre[x];
}
signed main(){
Ios;
cin>>T;
while(T--)
{
cin>>n;
for(int i=1;i<n;i++) cin>>a[i].x>>a[i].y>>a[i].w;
sort(a+1, a+n, cmp);
for(int i=1;i<=n;i++) pre[i] = i, siz[i] = 1;
int ans = 0;
for(int i=1;i<n;i++)
{
int x = a[i].x, y = a[i].y, w = a[i].w;
ans += (w+1)*(siz[find(x)]*siz[find(y)]-1);
siz[find(y)] += siz[find(x)];
pre[find(x)] = find(y);
}
cout << ans <<endl;
}
return 0;
}