【图论】1 (最小生成树&虚拟点思想)C.戴森球计划 题解

一. 题目

题目描述

输入输出格式

样例

样例1

样例2 & 样例解释

数据范围

二. 思路

对于前20%数据 解法

因为保证了 x i = 1 x_i = 1 xi=1,也就是说这些点都在 x = 1 x = 1 x=1 这条直线上。

那么最优解必定是在 c i c_i ci 最小的点上建发电站,然后把这些点之间全部联通即可。

即最终答案 a n s = min ⁡ 1 ≤ i ≤ n c i + max ⁡ 1 ≤ i ≤ n y i − min ⁡ 1 ≤ i ≤ n y i ans = \min_{1 \leq i \leq n}c_i + \max_{1 \leq i \leq n}y_i - \min_{1 \leq i \leq n} y_i ans=1≤i≤nminci+1≤i≤nmaxyi−1≤i≤nminyi

可见,我们要枚举 1 1 1 到 n n n ,所以时间复杂度为 O ( n ) O(n) O(n) 。

对于前70%数据 解法

思路分析

因为50%的数据没有什么特殊的地方(至少作者没有想到),所以直接从70%的数据入手。

思考: k i = 1 k_i = 1 ki=1 ,也就是点之间的连线每条边的单位花费为 1 + 1 = 2 1+1=2 1+1=2 ,而 1 0 8 ≤ c i ≤ 1 0 9 10^8 \leq c_i \leq 10^9 108≤ci≤109 很大,并且在 n ≤ 2000 n \leq 2000 n≤2000 的情况下,我们发现连线的花费往往小于构建发电站的花费 。所以容易得出贪心策略在 c i c_i ci 最小的点建发电站,其他点都与这个点联通

这个问题就等价于要把图上的每一个点都联通,并且花费最小

先考虑如何联通每一个点:构建一棵树 ,共有 n n n 个点和 n − 1 n-1 n−1 条边。

其次要让花费小:这不就是最小生成树吗?!

具体实现

我们先把 n n n 个点之间两两连线,每条边的权值为该边的花费,构建一个完全图 ,然后对这张图 跑Kruskal或Prim(最小生成树) 即可。

对于100%数据 解法

发现100%的数据与70%的数据对比, k i k_i ki 与 c i c_i ci 的取值范围更广泛 ,所以不一定只有一个发电站是最优解

那么按照70%数据的思路,我们对于每一个发电站,都会构建出一棵最小生成树 ,但一棵不一定涵盖所有点 。即这张图变成了一个最小生成树组成的森林

这样的话,我们没法枚举每个发电站再构建最小生成树。能不能把这些树合在一起呢?

这里,我们引入 "虚拟点思想" ,也叫做 "超级零点" 。也就是新增0号(或不在范围内的)点,并把它向其他点都连一条边 ,边的权值为在与其相连的点构建发电站的花费

这样,我们就可以对这个图跑最小生成树即可。因为最小生成树一定会连向虚拟点 ,所以至少会构建一个发电站,思路可行。

三. 代码实现

20pts 解法

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 2055;
long long id,n,k[N],x[N],y[N],maxx,minn=1e17,ress=1e17;
long long c[N],f[N],res = 1e17,ans = 0,cnt;
struct node
{
	long long u,v,w;
}a[N*N*2];
int main()
{
	freopen("electricity.in","r",stdin);
	freopen("electricity.out","w",stdout);
	cin >> id;
	cin >> n;
	for (int i=1;i<=n;i++)
	{
		scanf("%lld%lld",&x[i],&y[i]);
	}
	for (int i=1;i<=n;i++)
	{
		scanf("%lld",&c[i]);
	}
	for (int i=1;i<=n;i++)
	{
		scanf("%lld",&k[i]);
	}
	
	if (1 <= id && id <= 4)
	{
		for (int i=1;i<=n;i++)
		{
			maxx = max(maxx,y[i]);
			minn = min(minn,y[i]);
			ress = min(ress,c[i]);
		}
		cout<<(maxx-minn)*2+ress;
		return 0;
	}
	return 0;
}

70pts 解法

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 2005;
long long id,n,k[N],x[N],y[N],maxx,minn=1e17,ress=1e17;
long long c[N],f[N],res = 1e17,ans = 1e17,cnt;
struct node
{
	long long u,v,w;
}a[N*N];
long long zabs(long long s)
{
	if (s > 0) return s;
	return -s;
}
long long dis(long long a,long long b)
{
	return zabs(x[a]-x[b])+zabs(y[a]-y[b]);
}
void add(long long h,long long b,long long c)
{
	a[++cnt].u = h;
	a[cnt].v = b;
	a[cnt].w = c;
}
bool cmp(node x,node y)
{
	return x.w < y.w;
}
long long fnd(int x)
{
	if (x == f[x]) return x;
	return f[x] = fnd(f[x]);
}
int main()
{
	freopen("electricity.in","r",stdin);
	freopen("electricity.out","w",stdout);
	cin >> id;
	cin >> n;
	for (int i=1;i<=n;i++)
	{
		scanf("%lld%lld",&x[i],&y[i]);
	}
	for (int i=1;i<=n;i++)
	{
		scanf("%lld",&c[i]);
	}
	for (int i=1;i<=n;i++)
	{
		scanf("%lld",&k[i]);
	}
	ans = 1e17;
	for (int i=1;i<=n;i++)
	{
		ans = mins(ans,c[i]);
	}
	for (int i=1;i<n;i++)
	{
		for (int j=i+1;j<=n;j++)
		{
			add(j,i,dis(i,j)*(k[i]+k[j]));
			add(i,j,dis(i,j)*(k[i]+k[j]));
		}
	}
	sort(a+1,a+cnt+1,cmp);
	long long tot = 0;
	for (int j=1;j<=n;j++)
	{
		f[j] = j;
	}
	for (int j=1;j<=cnt;j++)
	{
		long long p1 = fnd(f[a[j].u]);
		long long p2 = fnd(f[a[j].v]);
		if (f[p1] == f[p2]) continue;
		f[p1] = fnd(f[p2]);
		ans += a[j].w;
		tot++;
		if (tot+1 == n) break;
	}
	cout<<ans;
	return 0;
}

100pts 解法

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 2055;
long long id,n,k[N],x[N],y[N],maxx,minn=1e17,ress=1e17;
long long c[N],f[N],res = 1e17,ans = 0,cnt;
struct node
{
	long long u,v,w;
}a[N*N*2];
long long zabs(long long s)
{
	if (s > 0) return s;
	return -s;
}
long long dis(long long a,long long b)
{
	return zabs(x[a]-x[b])+zabs(y[a]-y[b]);
}
void add(long long h,long long b,long long c)
{
	a[++cnt].u = h;
	a[cnt].v = b;
	a[cnt].w = c;
}
bool cmp(node x,node y)
{
	return x.w < y.w;
}
long long fnd(int x)
{
	if (x == f[x]) return x;
	return f[x] = fnd(f[x]);
}
int main()
{
	freopen("electricity.in","r",stdin);
	freopen("electricity.out","w",stdout);
	cin >> id;
	cin >> n;
	for (int i=1;i<=n;i++)
	{
		scanf("%lld%lld",&x[i],&y[i]);
	}
	for (int i=1;i<=n;i++)
	{
		scanf("%lld",&c[i]);
	}
	for (int i=1;i<=n;i++)
	{
		scanf("%lld",&k[i]);
	}
	ans = 0;
	//构图
	for (int i=1;i<n;i++)
	{
		for (int j=i+1;j<=n;j++)
		{
			add(j,i,dis(i,j)*(k[i]+k[j]));
			add(i,j,dis(i,j)*(k[i]+k[j]));
		}
	}
	//虚拟点构建
	for (int i=1;i<=n;i++)
	{
		add(n+1,i,c[i]);
		add(i,n+1,c[i]);
	}
	sort(a+1,a+cnt+1,cmp);
	long long tot = 0;
	for (int j=1;j<=n+1;j++)
	{
		f[j] = j;
	}
	//Kruskal最小生成树
	for (int j=1;j<=cnt;j++)
	{
		long long p1 = fnd(f[a[j].u]);
		long long p2 = fnd(f[a[j].v]);
		if (f[p1] == f[p2]) continue;
		f[p1] = fnd(f[p2]);
		ans += a[j].w;
		tot++;
		if (tot == n) break;
	}
	cout<<ans;
	return 0;
}

四. 总结

这道图论题在CSP-J模拟赛放了T3,感觉略难(最小生成树和虚拟点思想略有超纲),但不引进"虚拟点思想"的70分给的很足。总体来说是一道练习最小生成树和虚拟点思想的好题。

相关推荐
湖南罗泽南几秒前
Windows C++ TCP/IP 两台电脑上互相传输字符串数据
c++·windows·tcp/ip
五味香1 分钟前
Linux学习,ip 命令
linux·服务器·c语言·开发语言·git·学习·tcp/ip
Chef_Chen10 分钟前
从0开始学习机器学习--Day22--优化总结以及误差作业(上)
人工智能·学习·机器学习
手握风云-20 分钟前
零基础Java第十六期:抽象类接口(二)
数据结构·算法
虾球xz25 分钟前
游戏引擎学习第11天
stm32·学习·游戏引擎
心怀梦想的咸鱼28 分钟前
Ue5 umg学习(三)文本控件
学习·ue5
心怀梦想的咸鱼29 分钟前
Ue5 umg学习(二)图像控件,锚点
学习·ue5
可均可可42 分钟前
C++之OpenCV入门到提高005:005 图像操作
c++·图像处理·opencv·图像操作
zyx没烦恼1 小时前
【STL】set,multiset,map,multimap的介绍以及使用
开发语言·c++
机器视觉知识推荐、就业指导1 小时前
基于Qt/C++与OpenCV库 实现基于海康相机的图像采集和显示系统(工程源码可联系博主索要)
c++·qt·opencv