P8074 [COCI2009-2010#7] SVEMIR 题解
前置知识:最小生成树。
给出
这道题的难点在于存边。
如果
而我们发现数据范围:
那么这样我们要存
考虑优化存边方法。
必须删边。
删边,就要删掉那些对于答案没有贡献的边,这些边可有可无。
我们来看一个例子:
3
1 1 1
1 1 2
1 1 3
我们实际上并不需要连三条边,我们只用在第一个点和第二个点、第二个点和第三个点之间各连一条边就可以了。
得出思路:分别按照x、y、z从小到大排三次序,排完后再数组中相邻两项之间建边。
这样我们最多只用建
显然这道题建立的是一个稀疏图,我们用 Kruskal 求解最小生成树。
代码奉上:
#include <iostream>
#include <algorithm>
using namespace std;
int n,m;//m为最后的边数
const int Maxn=1e5+5,Maxm=1e6+5;
struct Node//存点,num为点的编号
{
int x,y,z,num;
}node[Maxn];
struct Edge//存边
{
int u,v,w;
}graph[Maxm];
//前三个cmp是用在建边时的排序的,最后一个是用在kruskal中的排序的
bool cmpx(Node n1,Node n2) {return n1.x<n2.x;}
bool cmpy(Node n1,Node n2) {return n1.y<n2.y;}
bool cmpz(Node n1,Node n2) {return n1.z<n2.z;}
bool cmpw(Edge e1,Edge e2) {return e1.w<e2.w;}
int fa[Maxn];//并查集优化
int mst;
int find(int x)
{
if(fa[x]==x) return x;
return fa[x]=find(fa[x]);
}
void merge(int x,int y) {fa[find(x)]=find(y);}
void kruskal()
{
for(int i=1;i<=n;i++) fa[i]=i;
int count=0;
sort(graph+1,graph+m+1,cmpw);
for(int i=1;i<=m;i++)
{
Edge e=graph[i];
int u=e.u,v=e.v,w=e.w;
if(find(u)!=find(v))
{
merge(u,v);
mst+=w;
count++;
if(count==n-1) return;
}
}
}
void add(int u,int v,int w)
{
graph[++m].u=u;
graph[m].v=v;
graph[m].w=w;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d%d%d",&node[i].x,&node[i].y,&node[i].z);
node[i].num=i;
}
sort(node+1,node+n+1,cmpx);
for(int i=1;i<n;i++) add(node[i].num,node[i+1].num,node[i+1].x-node[i].x);
sort(node+1,node+n+1,cmpy);
for(int i=1;i<n;i++) add(node[i].num,node[i+1].num,node[i+1].y-node[i].y);
sort(node+1,node+n+1,cmpz);
for(int i=1;i<n;i++) add(node[i].num,node[i+1].num,node[i+1].z-node[i].z);
kruskal();
cout<<mst;
return 0;
}