【Codeforces 704E】Iron Man

树链剖分,计算几何,扫描线。

题目大意:

给定一棵树,有 $m$ 个人,每个人出现的时刻为 $t_i$,以每一单位时间 $c_i$ 条边的速度从 $u_i$ 向 $v_i$ 匀速运动,到终点后消失(若 $u_i=v_i$,则这个人在 $t_i$ 时刻出现后立即消失)。

问最早在哪一时刻,有两人正好相遇。若不存在这样的时刻,输出 $-1$。

题解:

考虑序列上的问题。

不难发现,每个人的时间-位置图像是个一次函数,令横坐标为时刻,纵坐标为位置。我们要求若干条线段最早的交点。

这个可以使用 set + 扫描线解决。具体地,每次在一条线段的开头处插入线段,和左右两条线段判相交。在一条线段的结尾处删除线段,并把它左右两边的线段判相交。直到当前考虑的横坐标超过最早的交点的横坐标。

set 可以使用自定义比较器,以每个点当前横坐标下的纵坐标为关键字。由于在第一次相交时就退出,所以之前都保证线段的相对位置关系不变,则平衡树的结构不变,不会出现问题。

时间复杂度 $O(n\log n)$。

考虑树上的问题,不难想到树链剖分,把每个人走的路径拆成若干条链判断。

对每条重链跑一遍扫描线即可。

注意精度问题。这里求线段相交时,直接解一次函数可能会有精度问题。我们可以记录每个人的速度,然后做一个相遇/追及问题,即可提高精度。

时间复杂度 $O(n\log^2 n)$。注意各种细节。

Code:

#include<cstdio>
#include<set>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
const int N=114514;
const double eps=1e-9;
int n,m,head[N],cnt,fa[N],dep[N],son[N],sz[N],top[N];
struct edge{
    int to,nxt;
}e[N<<1];
struct point{
    double x,y;
    inline point operator-(const point&rhs)const{return(point){x-rhs.x,y-rhs.y};}
    inline bool operator<(const point&rhs)const{return x<rhs.x;}
};
vector<pair<point,point> >Vec;
vector<int>VV;
double X;
struct com{
    inline bool operator()(int a,int b)const{
        return Vec[a].first.y+(X-Vec[a].first.x)*VV[a]+eps<Vec[b].first.y+(X-Vec[b].first.x)*VV[b];
    }
};
struct DataStructure{
    vector<pair<point,point> >vec;
    vector<int>V;
    set<int,com>st;
    struct data{
        double x,y;
        int a,b;
        inline bool operator<(const data&rhs)const{return fabs(x-rhs.x)<eps?a>rhs.a:x<rhs.x;}
    };
    void insert(double t1,double t2,double x1,double x2,int c){
        vec.push_back(make_pair((point){t1,x1},(point){t2,x2}));
        if(x1>x2)c=-c;
        V.push_back(c);
    }
    inline double cross(point a,point b){
        return a.x*b.y-a.y*b.x;
    }
    inline double is_cross(int ia,int ib){
        double _1=cross(vec[ia].second-vec[ia].first,vec[ib].first-vec[ia].first);
        double _2=cross(vec[ia].second-vec[ia].first,vec[ib].second-vec[ia].first);
        if(!(_1-eps<0&&_2+eps>0||_1+eps>0&&_2-eps<0))return 0;
        _1=cross(vec[ib].second-vec[ib].first,vec[ia].first-vec[ib].first);
        _2=cross(vec[ib].second-vec[ib].first,vec[ia].second-vec[ib].first);
        if(!(_1-eps<0&&_2+eps>0||_1+eps>0&&_2-eps<0))return 0;
        return 1;
    }
    inline point get_dot(int ia,int ib){
        double s=vec[ib].first.x*V[ib]-vec[ia].first.x*V[ia]-vec[ib].first.y+vec[ia].first.y;
        if(fabs(V[ia]-V[ib])<eps){
            if(fabs(s)<=eps)return max(vec[ia].first,vec[ib].first);
            return(point){1e18,1e18};
        }
        double x=(s)/(V[ib]-V[ia]);
        return(point){x,114514};
    }
    vector<data>vc;
    void work(double&ans){
        Vec=vec,VV=V;
        for(int i=0;i<vec.size();++i)
        vc.push_back((data){vec[i].first.x,vec[i].first.y,i,-1}),
        vc.push_back((data){vec[i].second.x,vec[i].second.y,-1,i});
        sort(vc.begin(),vc.end());
        point ss=(point){1e18,1e18};
        for(int I=0;I<vc.size();++I){
            data tp=vc[I];
            if(ans-eps<tp.x)break;
            if(tp.x>ss.x-eps)break;
            X=tp.x;
            if(tp.a!=-1){
                int id=tp.a;
                auto it=st.lower_bound(tp.a);
                if(it!=st.end()){
                    if(is_cross(*it,id)){
                        point p=get_dot(*it,id);
                        ss=min(ss,p);
                    }
                }
                if(it!=st.begin()){
                    --it;
                    if(is_cross(*it,id)){
                        point p=get_dot(*it,id);
                        ss=min(ss,p);
                    }
                }
                st.insert(id);
            }else{
                int id=tp.b;
                auto qwq=st.lower_bound(id);
                st.erase(qwq);
                auto it=st.lower_bound(tp.a);
                if(it!=st.end()&&it!=st.begin()){
                    auto itt=it;--itt;
                    if(is_cross(*it,*itt)){
                        point p=get_dot(*it,*itt);
                        ss=min(ss,p);
                    }
                    if(is_cross(*it,id)){
                        point p=get_dot(*it,id);
                        ss=min(ss,p);
                    }
                    if(is_cross(id,*itt)){
                        point p=get_dot(id,*itt);
                        ss=min(ss,p);
                    }
                }
            }
        }
        st.clear();
        ans=min(ans,ss.x);
    }
}A[N];
void dfs(int now){
    sz[now]=1;
    for(int i=head[now];i;i=e[i].nxt)
    if(!dep[e[i].to]){
        dep[e[i].to]=dep[now]+1,fa[e[i].to]=now;
        dfs(e[i].to);
        sz[now]+=sz[e[i].to];
        if(!son[now]||sz[son[now]]<sz[e[i].to])son[now]=e[i].to;
    }
}
void dfs2(int now){
    if(son[now])top[son[now]]=top[now],dfs2(son[now]);
    for(int i=head[now];i;i=e[i].nxt)
    if(e[i].to!=son[now]&&dep[e[i].to]>dep[now])dfs2(top[e[i].to]=e[i].to);
}
inline int LCA(int x,int y){
    while(top[x]!=top[y])
    if(dep[top[x]]>dep[top[y]])x=fa[top[x]];else y=fa[top[y]];
    return dep[x]<dep[y]?x:y;
}
inline int dist(int x,int y,int lca=0){
    if(!lca)lca=LCA(x,y);
    return dep[x]+dep[y]-2*dep[lca];
} 
void insert(int t,int c,int x,int y){
    int lca=LCA(x,y);
    double tu=t,tv=t+1.*dist(x,y,lca)/c;
    while(top[x]!=top[y])
    if(dep[top[x]]>dep[top[y]]){
        A[top[x]].insert(tu,tu+1.*(dep[x]-dep[fa[top[x]]])/c,dep[x],dep[fa[top[x]]],c);
        tu+=1.*(dep[x]-dep[fa[top[x]]])/c;
        x=fa[top[x]];
    }else{
        A[top[y]].insert(tv-1.*(dep[y]-dep[fa[top[y]]])/c,tv,dep[fa[top[y]]],dep[y],c);
        tv-=1.*(dep[y]-dep[fa[top[y]]])/c;
        y=fa[top[y]];
    }
    A[top[x]].insert(tu,tv,dep[x],dep[y],c);
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<n;++i){
        int u,v;
        scanf("%d%d",&u,&v);
        e[++cnt]=(edge){v,head[u]},head[u]=cnt;
        e[++cnt]=(edge){u,head[v]},head[v]=cnt;
    }
    dfs(dep[1]=1),dfs2(top[1]=1);
    for(int i=1;i<=m;++i){
        int t,c,u,v;
        scanf("%d%d%d%d",&t,&c,&u,&v);
        insert(t,c,u,v);
    }
    double ans=1e17;
    for(int i=1;i<=n;++i)
    if(top[i]==i)A[i].work(ans);
    if(ans>1e16)puts("-1");else printf("%.8f\n",ans);
    return 0;
}