CF504E Iron Man

20 篇文章 0 订阅
5 篇文章 0 订阅

CF504E Iron Man

  • 一个边权为 1 1 1 n n n个点的树上有 m m m个人在运动,第 i i i个人从 v i v_i vi c i c_i ci的速度在 t i t_i ti时刻出发前往 u i u_i ui

  • 求最早什么时候有两个人相遇(可以在边上)。

  • n , m ≤ 1 e 5 n,m\le1e5 n,m1e5

Solution

  • 首先树剖,变成链上的问题。
  • 如果以时间为横坐标,深度为纵坐标,那么一个运动的人就是一条线段,现在要求若干条线段最早在什么地方相交。
  • 显然一条线段相交的线段是在它加入的时候距离它最近的,否则在更早的时候也可以相交。
  • 所以我们在时间未到当前交点时,对于每一个线段 y = k t + b y=kt+b y=kt+b维护一个set.按照时间扫描线,加入和删除的时候找相邻的线段。
  • 因为在此之前没有相交,所以我们可以把 t t t设为全局变量,直接用 s e t set set也没有问题,除非找到了一个最小的交点。
  • 需要注意精度。
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<vector>
#include<set>
#define maxn 100005
#define db double 
#define E 0.00000001
using namespace std;

int n,m,i,j,k,em,e[maxn*2],nx[maxn*2],ls[maxn];
int tot,dfn[maxn],sz[maxn],g[maxn],top[maxn],fa[maxn],dep[maxn];
struct path{
	db k,b,t1,t2; int i;
	path(int _x=0,int _y=0,db _t1=0,db _t2=0,db _v=0){
		k=_v,t1=_t1,t2=_t2,b=_x-t1*k;
	}
};
vector<path> p[maxn];

void insert(int x,int y){
	em++; e[em]=y; nx[em]=ls[x]; ls[x]=em;
	em++; e[em]=x; nx[em]=ls[y]; ls[y]=em;
}

void dfs1(int x,int p){
	sz[x]=1,fa[x]=p;
	for(int i=ls[x];i;i=nx[i]) if (e[i]!=p){
		dfs1(e[i],x),sz[x]+=sz[e[i]];
		g[x]=(!g[x]||sz[g[x]]<sz[e[i]])?e[i]:g[x];
	}
}

void dfs2(int x,int p){
	dfn[x]=++tot,dep[x]=dep[p]+1;
	if (g[x]) top[g[x]]=top[x],dfs2(g[x],x);
	for(int i=ls[x];i;i=nx[i]) if (e[i]!=p&&e[i]!=g[x]) 
		top[e[i]]=e[i],dfs2(e[i],x);
}

int getlca(int x,int y){
	while (top[x]!=top[y]){
		if (dep[top[x]]<dep[top[y]]) swap(x,y);
		x=fa[top[x]];
	}
	return (dep[x]<dep[y])?x:y;
}

void addit(){
	int x,y,z; db v,t1,t2;
	scanf("%lf%lf%d%d",&t1,&v,&x,&y);
	z=getlca(x,y),t2=t1+(dep[x]+dep[y]-dep[z]*2)/v;
	while (top[x]!=top[y]){
		if (dep[top[x]]>dep[top[y]]){
			p[top[x]].push_back(path(dep[x],dep[top[x]]-1,t1,t1+(dep[x]-dep[top[x]]+1)/v,-v));
			t1+=(dep[x]-dep[top[x]]+1)/v;
			x=fa[top[x]];
		} else {
			p[top[y]].push_back(path(dep[top[y]]-1,dep[y],t2-(dep[y]-dep[top[y]]+1)/v,t2,v));
			t2-=(dep[y]-dep[top[y]]+1)/v;
			y=fa[top[y]];
		}
	}
	p[top[x]].push_back(path(dep[x],dep[y],t1,t2,(dep[x]<dep[y])?v:-v));
}

struct arr{int i,t;db tim;} q[maxn*2];
int cmp(arr a,arr b){
	if (abs(a.tim-b.tim)<E) return a.t>b.t;
	return a.tim<b.tim;
}
db tim; 
set<path> s;
set<path>::iterator pre,nex,it;
int operator<(path a,path b){return a.k*tim+a.b<b.k*tim+b.b;}
int pd(db a,db b){return a<b||abs(a-b)<=E;}
db check(path a,path b){
	if (abs(a.k-b.k)<=E) {
		if (abs(a.b-b.b)<=E) 
			return pd(max(a.t1,b.t1),min(a.t2,b.t2))?max(a.t1,b.t1):2e9;
		return 2e9;
	}
	db t=(a.b-b.b)/(b.k-a.k);
	if (pd(a.t1,t)&&pd(b.t1,t)&&pd(t,a.t2)&&pd(t,b.t2)) return t;
	return 2e9;
}

db ans;
void doit(int t){
	int cnt=0;
	for(i=0;i<p[t].size();i++) {
		p[t][i].i=i;
		cnt++,q[cnt].i=i,q[cnt].t=1,q[cnt].tim=p[t][i].t1;
		cnt++,q[cnt].i=i,q[cnt].t=-1,q[cnt].tim=p[t][i].t2;
	}
	sort(q+1,q+1+cnt,cmp),s.clear();
	tim=0; db mi=2e9;
	for(i=1;i<=cnt;i++)  {
		tim=q[i].tim,k=q[i].i;
		if (pd(mi,tim)) break;
		if (q[i].t>0){
			nex=s.lower_bound(p[t][k]);
			if (nex!=s.begin()) pre=nex,pre--; else pre=s.end();
			if (pre!=s.end()) mi=min(mi,check(*pre,p[t][k]));
			if (nex!=s.end()) mi=min(mi,check(*nex,p[t][k]));
			s.insert(p[t][k]);
		} else {
			it=s.find(p[t][k]);
			if (it!=s.begin()) pre=it,pre--; else pre=s.end();
			nex=it,nex++;
			s.erase(it);
			if (pre!=s.end()&&nex!=s.end()) mi=min(mi,check(*pre,*nex));
		}
	}
	ans=min(ans,mi);
}

int main(){
	freopen("ceshi.in","r",stdin);
	scanf("%d%d",&n,&m);
	for(i=1;i<n;i++) scanf("%d%d",&j,&k),insert(j,k);
	dfs1(1,0);
	top[1]=1,dfs2(1,0);
	for(i=1;i<=m;i++) addit();
	ans=2e9;
	for(int now=1;now<=n;now++) if (now==top[now]) 
		doit(now);
	if (ans==2e9) printf("-1");
	else printf("%.10lf",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值