1279 - Asteroid Rangers

这里写图片描述
这里写图片描述
这里写图片描述

我有话说:
这道题紫书上给出的提示是,在一个新最小生成树生成的时候一定会有两条边欧几里德距离相等。这是因为按照题意,任何情况下,形成的二叉树只有一种,即所有的边长度都不等。所以我们先按照题目给出的数据计算预判发生交换的时间点,我们称之为事件点,按照发生时间先后进行排序。并且逐个检验,是否有新的二叉树生成。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#include <vector>
#include <cmath>

using namespace std;
const int maxn=50+5;
const int maxks=maxn*(maxn+1)*2;
const double eps=1e-8;

int n,nks;
struct Event{
  double t;
  int newks,oldks;
  Event(double t=0,int newks=0,int oldks=0):t(t),newks(newks),oldks(oldks){}
  bool operator<(const Event& rhs)const{
    return t-rhs.t<0;
  }
};
vector<Event>events;
struct KineticPoint{
  double x,y,z;
  double dx,dy,dz;
  void read(){
    scanf("%lf%lf%lf%lf%lf%lf",&x,&y,&z,&dx,&dy,&dz);
  }
}kp[maxn];
struct KineticSegment{
  double a,b,c;
  int u,v;
  bool operator<(const KineticSegment& rhs)const{
    return c-rhs.c<0;
  }
}ks[maxks];
inline double sqr(double x){return x*x;}
int pa[maxn];
void init_ufset(){for(int i=0;i<n;i++)pa[i]=i;}
int findset(int x){return pa[x]==x?x:pa[x]=findset(pa[x]);}
void make_segments(){
    nks=0;
    for(int i=0;i<n;i++){
        for(int j=i+1;j<n;j++){
            //距离计算为dist=sum{((kp[i].dx-kp[j].dx)*t+(kp[i].x-kp[j].x))^2}
            //换算成二次函数的一般形式:at^2+b^t+c;
            ks[nks].a=sqr(kp[i].dx-kp[j].dx)+sqr(kp[i].dy-kp[j].dy)+sqr(kp[i].dz-kp[j].dz);
            ks[nks].b=2*((kp[i].x-kp[j].x)*(kp[i].dx-kp[j].dx)+(kp[i].y-kp[j].y)*(kp[i].dy-kp[j].dy)+(kp[i].z-kp[j].z)*(kp[i].dz-kp[j].dz));
            ks[nks].c=sqr(kp[i].x-kp[j].x)+sqr(kp[i].y-kp[j].y)+sqr(kp[i].z-kp[j].z);
            ks[nks].u=i;
            ks[nks].v=j;
            nks++;
        }
    }
    sort(ks,ks+nks);
}
void make_events(){
    events.clear();
    for(int i=0;i<nks;i++){
        for(int j=i+1;j<nks;j++){
            int s1=i,s2=j;
            if(ks[s1].a-ks[s2].a<0)s1=j,s2=i;//s1更大
            double a=ks[s1].a-ks[s2].a;
            double b=ks[s1].b-ks[s2].b;
            double c=ks[s1].c-ks[s2].c;
            if(fabs(a)<eps){//bt+c=0;
               if(fabs(b)<eps)continue;
               if(b>0){swap(s1,s2);b=-b;c=-c;}
               if(c>0){events.push_back(Event(-c/b,s1,s2));}
               continue;
            }
            double delta=b*b-4*a*c;
            if(delta<eps)continue;
            delta=sqrt(delta);
            double t1=(-b-delta)/(a*2);
            double t2=(-b+delta)/(a*2);//t1<t2
            if(t1>0)events.push_back(Event(t1,s1,s2));
            if(t2>0)events.push_back(Event(t2,s2,s1));
        }
    }
    sort(events.begin(),events.end());
}
int solve(){
    int pos[maxks];//pos[i]是在最小生成树中第i个 segment。0表示其不在树中。
    int e[maxn];//e[i]表示在最小生成树中的第i个edge。pos[e[i]]=i;

    init_ufset();
    for(int i=0;i<nks;i++)pos[i]=0;
    int idx=0;
    for(int i=0;i<nks;i++){
        int u=findset(ks[i].u),v=findset(ks[i].v);
        if(u!=v){
            e[pos[i]=++idx]=i;
            pa[u]=v;
        }
        if(idx==n-1)break;
    }
    int ans=1;
    for(int i=0;i<events.size();i++){
        if(pos[events[i].oldks]&&(!pos[events[i].newks])){
            init_ufset();
            int oldpos=pos[events[i].oldks];
            for(int j=1;j<n;j++){
                if(j!=oldpos){
                    int u=findset(ks[e[j]].u),v=findset(ks[e[j]].v);
                    if(u!=v){
                        pa[u]=v;
                    }
                }
            }
            int u=findset(ks[events[i].newks].u),v=findset(ks[events[i].newks].v);
            if(u!=v){//新的二叉树。
                ans++;
                pos[events[i].newks]=oldpos;
                e[oldpos]=events[i].newks;
                pos[events[i].oldks]=0;
            }
        }
    }
    return ans;
}
int main()
{
    int kase=0;
    while(scanf("%d",&n)==1){
        for(int i=0;i<n;i++)kp[i].read();
        make_segments();
        make_events();
        int ans=solve();
        printf("Case %d: %d\n",++kase,ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值