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

一. 题目

题目描述

题目描述

输入输出格式

输入输出格式

样例

样例1

样例1

样例2 & 样例解释

样例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=1inminci+1inmaxyi1inminyi

可见,我们要枚举 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 108ci109 很大,并且在 n ≤ 2000 n \leq 2000 n2000 的情况下,我们发现连线的花费往往小于构建发电站的花费。所以容易得出贪心策略 c i c_i ci 最小的点建发电站,其他点都与这个点联通

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

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

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

具体实现

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

对于100%数据 解法

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

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

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

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

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

三. 代码实现

20pts 解法

#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 解法

#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 解法

#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分给的很足。总体来说是一道练习最小生成树和虚拟点思想的好题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值