一. 题目
题目描述
输入输出格式
样例
样例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分给的很足。总体来说是一道练习最小生成树和虚拟点思想的好题。