题解:P17126 [ICPC 2025 Shanghai R] Flower' s land 4
lailai0916
·
·
题解
题意简述
给定若干连接非负 x 轴与非负 y 轴的线段。
每次询问给出一条从 x 轴出发、终点位于第一象限或坐标轴上的线段。
判断它是否与任意给定线段相交,端点相交也算。
解题思路
先只考虑 x_i>0 且 y_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,j 在 z 处的高度时,不进行除法。
因为 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。
询问与它相交,当且仅当:
-
- 或 b=0 且 c\le v_y,此时询问终点落在线段上。
原点线段同时属于两类退化判断,不会影响布尔答案。
正确性证明
对于非退化给定线段,询问线段完全位于第一象限闭区域。
两条线段相交等价于询问两个端点分处给定直线两侧或在线上。
起点相对直线的位置只由 a 与 x_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;
}
```