题解:P17126 [ICPC 2025 Shanghai R] Flower' s land 4

· · 题解

题意简述

给定若干连接非负 x 轴与非负 y 轴的线段。 每次询问给出一条从 x 轴出发、终点位于第一象限或坐标轴上的线段。 判断它是否与任意给定线段相交,端点相交也算。

解题思路

先只考虑 x_i>0y_i>0 的给定线段。 它连接 (x_i,0)(0,y_i),所在直线在横坐标 z 处的高度为:

h_i(z)=\frac{y_i(x_i-z)}{x_i}

这条直线在第一象限中的部分恰好是给定线段。 询问线段的两个端点都在第一象限或边界上。 因此,它与给定线段相交, 当且仅当两个询问端点位于该直线两侧,或至少一个端点在直线上。 交点仍在第一象限内,所以一定落在给定线段上。

询问起点为 (a,0)。 若 a\le x_i,起点位于直线下方或线上。 此时询问终点 (b,c) 必须位于直线上方或线上,即:

h_i(b)\le c

于是第一类相交条件为:

\min_{x_i\ge a}h_i(b)\le c

a\ge x_i,起点位于直线上方或线上, 终点必须位于直线下方或线上,即 h_i(b)\ge c。 第二类相交条件为:

\max_{x_i\le a}h_i(b)\ge c

a=x_i 时,询问起点就是给定线段的端点。 两个条件都允许等号, 而任意实数都满足不大于或不小于 c 中的至少一个, 所以端点相交不会被遗漏。

把给定线段按 x_i 排序,把询问按 a 排序。 从大到小扫描询问时,逐步加入所有 x_i\ge a 的直线, 维护它们在横坐标 b 处的最小高度。 从小到大再扫描一次, 加入所有 x_i\le a 的直线并维护最大高度。

只需要在询问出现过的横坐标 b 上求值。 将这些坐标排序去重后,使用离散李超树维护最小值或最大值。 比较直线 i,jz 处的高度时,不进行除法。 因为 x_i,x_j>0,有:

h_i(z)<h_j(z)\iff y_i(x_i-z)x_j<y_j(x_j-z)x_i

所有乘法使用 128 位整数,避免溢出与浮点误差。

还需单独处理退化线段。

y_i=0,给定线段是 x 轴上的区间 [0,x_i]。 记所有这类线段的最大右端点为 h_x。 当询问终点满足 c>0 时,询问只在起点接触 x 轴, 相交条件为 a\le h_x。 当 c=0 时,询问也位于 x 轴上, 相交条件为 \min(a,b)\le h_x

x_i=0,给定线段是 y 轴上的区间 [0,y_i]。 记最大上端点为 v_y。 询问与它相交,当且仅当:

原点线段同时属于两类退化判断,不会影响布尔答案。

正确性证明

对于非退化给定线段,询问线段完全位于第一象限闭区域。 两条线段相交等价于询问两个端点分处给定直线两侧或在线上。 起点相对直线的位置只由 ax_i 的大小决定, 所以所有可能相交分别被两式

第一次扫描中的李超树恰好包含全部 $x_i\ge a$ 的直线, 查询其最小值便等价于判断第一式是否对某条线成立。 第二次扫描同理,查询最大值恰好判断第二式。 因此,两次扫描准确处理所有非退化线段。 退化线段分别是从原点开始的 $x$ 轴区间或 $y$ 轴区间。 前述端点条件直接给出了它们与询问线段相交的充要条件。 所以退化判断也不重不漏,最终答案正确。 每条非退化直线在两次扫描中各插入一次, 每次询问各查询一次。 时间复杂度为 $O((n+q)\log q)$,空间复杂度为 $O(n+q)$。 ## 参考代码 ```cpp #include <bits/stdc++.h> using namespace std; using ll=long long; using i128=__int128_t; const int N=1000005; struct line { ll x,y; }l[N]; struct query { ll a,b,c; int p; }q[N]; ll v[N]; int ord[N]; bool ans[N]; int tree[N*4]; struct lichao { bool mx; bool better(int x,int y,ll z) { i128 a=(i128)l[x].y*(l[x].x-z)*l[y].x; i128 b=(i128)l[y].y*(l[y].x-z)*l[x].x; return mx?a>b:a<b; } void clear(int n,bool op) { fill(tree,tree+n*4,-1); mx=op; } void add(int u,int L,int R,int p) { if(tree[u]==-1) { tree[u]=p; return; } int m=(L+R)>>1; bool x=better(p,tree[u],v[L]); bool y=better(p,tree[u],v[m]); if(y)swap(p,tree[u]); if(L==R)return; if(x!=y)add(u<<1,L,m,p); else add(u<<1|1,m+1,R,p); } int ask(int u,int L,int R,int p) { int res=tree[u]; if(L==R)return res; int m=(L+R)>>1; int x=p<=m?ask(u<<1,L,m,p):ask(u<<1|1,m+1,R,p); if(res==-1)return x; if(x==-1)return res; return better(x,res,v[p])?x:res; } }tr; void solve() { int n,m; cin>>n>>m; int tot=0; ll hx=-1; ll vy=-1; for(int i=0;i<n;i++) { ll x,y; cin>>x>>y; if(y==0)hx=max(hx,x); if(x==0)vy=max(vy,y); if(x&&y)l[tot++]={x,y}; } for(int i=0;i<m;i++) { cin>>q[i].a>>q[i].b>>q[i].c; v[i]=q[i].b; ord[i]=i; ans[i]=0; ll x=q[i].c?q[i].a:min(q[i].a,q[i].b); if(x<=hx)ans[i]=1; if(vy>=0&&(q[i].a==0||(q[i].b==0&&q[i].c<=vy)))ans[i]=1; } sort(l,l+tot,[](const line &x,const line &y) { return x.x<y.x; }); sort(ord,ord+m,[](int x,int y) { return q[x].a<q[y].a; }); sort(v,v+m); int k=unique(v,v+m)-v; for(int i=0;i<m;i++)q[i].p=lower_bound(v,v+k,q[i].b)-v; tr.clear(k,0); int p=tot-1; for(int i=m-1;i>=0;i--) { int x=ord[i]; while(p>=0&&l[p].x>=q[x].a)tr.add(1,0,k-1,p--); int y=tr.ask(1,0,k-1,q[x].p); if(y>=0&&(i128)l[y].y*(l[y].x-q[x].b)<=(i128)q[x].c*l[y].x)ans[x]=1; } tr.clear(k,1); p=0; for(int i=0;i<m;i++) { int x=ord[i]; while(p<tot&&l[p].x<=q[x].a)tr.add(1,0,k-1,p++); int y=tr.ask(1,0,k-1,q[x].p); if(y>=0&&(i128)l[y].y*(l[y].x-q[x].b)>=(i128)q[x].c*l[y].x)ans[x]=1; } for(int i=0;i<m;i++) { if(ans[i])cout<<"YES"; else cout<<"NO"; cout<<'\n'; } } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int T; cin>>T; while(T--)solve(); return 0; } ```