题目
题目描述
有
n
n
n 条线段。线段端点都是整点。找到最小的
x
x
x,使得去掉输入中的第
x
x
x 条线段后,剩下的线段两两不相交。
数据保证有解,也保证给出的 n n n 条线段至少有一个交点。
数据范围与提示
n
⩽
1
0
5
n\leqslant 10^5
n⩽105,端点坐标是不超过
1
0
9
10^9
109 的非负整数。
思路
要是我能找到一个交点(也就是两条相交的线段),那 答案肯定是二者之一。我只需要再判断一下,这两条线段是否与其他线段相交(不是的那一条一定只有一个交点,就是我找到的那一个交点)。
问题就是,怎么找到这一对相交的线段?
使用 扫描线!用一条平行于
y
y
y 轴的直线从左往右扫描,遇到左端点处理一下,遇到右端点处理一下。完结撒花!
具体是什么处理呢?
- 左端点:将该线段加入 s e t set set,判断 它与它上面的、它与它下面的 是否相交。
- 右端点:将该线段删除,判断 它上面的与它下面的 是否相交。
什么是上面的、下面的?我们把该扫描线所截取到的每一个点画在数轴上,移动扫描线就是每一个点在左右移动(扫描线在某一线段上截取到的点,可以理解为 一个点在该线段上从左往右运动, y y y 值的变化必然是连续的)。也就是说,相邻的两个 y y y 值才可能逐渐靠近,并最终相交。所以 上下就是扫描线上的一个交点,并且这个交点恰好在当前线段的上面一个。
放几张图让你们理解一下。一堆图麻烦死了。
过
A
A
A 点、
B
B
B 点做的垂线的纵截距,就是对应
y
y
y 值。可以发现,这两个垂线是越来越靠近的,也就是纵截距越来越靠近(而且纵截距的变化是连续的,慢慢滑动)。对于
B
B
B 来说,它上面的点就是
A
A
A 。
由于是连续的滑动,所以只需要判断最近的两个(上面的和下面的)——在两条线段相交之前,上下关系不会改变。很明显,往下数第一条和往下数第二条不相交,即最近的那一条永远最近。要想相交,就是想要 y y y 值相同,也是先 “撞到” 最近的。用 s e t \tt set set 维护即可!到时候也好删除。
对了,删除!删除会导致它原本下面的和原本上面的相邻,就要再次检测是否相交。
由于检测相交是 O ( 1 ) \mathcal O(1) O(1) 的,所以总复杂度是 O ( n log n ) \mathcal O(n\log n) O(nlogn) 的。
代码
#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
using namespace __gnu_pbds;
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long int_;
inline int readint(){
int a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
inline void writeint(int x){
if(x < 0) putchar('-'), x = -x;
if(x > 9) writeint(x/10);
putchar((x-x/10*10)^48);
}
struct Point{
int x, y;
Point(){}
Point(int X,int Y):x(X),y(Y){}
Point operator - (const Point &t) const {
return Point(x-t.x,y-t.y);
}
int_ dot(const Point &p) const {
return int_(x)*p.x+int_(y)*p.y;
}
int_ det(const Point &p) const {
return int_(x)*p.y-int_(y)*p.x;
}
};
# define triangle(p1,p2,p3)\
(int_(p2.x-p1.x)*(p3.y-p1.y)\
-int_(p2.y-p1.y)*(p3.x-p1.x))
int getSgn(const int_ &x){
return (x < 0) ? -1 : (x > 0);
}
struct Line{
Point a, b;
bool cross(const Line &t) const {
if((b-a).det(t.b-t.a) == 0)
return (a-t.a).dot(b-t.a) <= 0
|| (t.a-a).dot(t.b-a) <= 0;
return getSgn(triangle(a,b,t.a))*
getSgn(triangle(a,b,t.b)) <= 0
&& getSgn(triangle(t.a,t.b,a))*
getSgn(triangle(t.a,t.b,b)) <= 0;
}
bool operator < (const Line &t) const {
return a.y < t.a.y;
}
double valueAt(const int &x){
return (double(b.y)*(x-a.x)+double(a.y)*(b.x-x))/(b.x-a.x);
}
};
const int MaxN = 100005;
Line line[MaxN];
int nowX; // current X-coordinate
struct CMP{
bool operator()(const int &x,const int &y){
return line[x].valueAt(nowX) < line[y].valueAt(nowX);
}
};
tree<int,null_type,CMP,rb_tree_tag> jh; // set
struct DDG{
int opt, id, x;
operator int() const { return x; }
};
DDG ddg[MaxN<<1];
int main(){
int n = readint(), L1 = 0, L2 = 0;
for(int i=1; i<=n; ++i){
line[i].a.x = readint();
line[i].a.y = readint();
line[i].b.x = readint();
line[i].b.y = readint();
if(line[i].a.x > line[i].b.x)
swap(line[i].a,line[i].b);
ddg[i<<1].opt = 1;
ddg[i<<1].x = line[i].a.x;
ddg[2*i-1].opt = -1;
ddg[2*i-1].x = line[i].b.x;
ddg[i<<1].id = ddg[2*i-1].id = i;
}
sort(ddg+1,ddg+(n<<1)+1);
for(int i=1; i<=(n<<1); ++i){
nowX = ddg[i].x; // important
if(ddg[i].opt == 1){
auto it = jh.lower_bound(ddg[i].id);
if(it != jh.end()){
L1 = ddg[i].id, L2 = *it; // expect
if(line[L1].cross(line[L2])) break;
}
if(it != jh.begin()){
L1 = ddg[i].id, L2 = *(-- it);
if(line[L1].cross(line[L2])) break;
}
jh.insert(ddg[i].id);
}
if(ddg[i].opt == -1){
jh.erase(ddg[i].id);
auto it = jh.lower_bound(ddg[i].id);
if(it != jh.end() && it != jh.begin()){
L1 = *it; L2 = *(-- it); // expect
if(line[L1].cross(line[L2])) break;
}
}
}
if(L1 > L2) swap(L1,L2);
bool ok = true;
rep(i,1,n) if(i != L1 && i != L2)
if(line[i].cross(line[L2])) ok = 0;
printf("%d\n",ok ? L1 : L2);
return 0;
}