(思维)洛谷 P3081 USACO13MAR Hill Walk 题解
题意
有 N(1≤N≤105)N(1 \le N \le 10 ^ 5)N(1≤N≤105)座小山,每座山所占的区域用直线 (x1,y1)(x1, y1)(x1,y1) 到 (x2,y2)(x2, y2)(x2,y2) 来表示(x1<x2x1 < x2x1<x2 并且 y1<y2y1 < y2y1<y2)。也就是说这些山用笛卡尔坐标系里的线段来表示,这些用于表示小山的线段都没有任何交点,第一座山的一端位于 (x1,y1)=(0,0)(x1, y1) = (0,0)(x1,y1)=(0,0)。
贝西从 (0,0)(0,0)(0,0) 开始在第一座山上漫步,一旦贝西到了一座山,她会一直走到该山的终点,这时,她会从边缘处起跳,如果她降落到另一座山上,她会继续在新的山上漫步。贝西起跳后沿 yyy 轴方向下落,如果贝西不能降落到一座山上,她会一直下落,直到到达 yyy 轴的负无穷大位置(y=−∞y = -\infiny=−∞)。
每座用线段表示的山 (x1,y1)→(x2,y2)(x1, y1) \to (x2, y2)(x1,y1)→(x2,y2) 包含 (x1,y1)(x1, y1)(x1,y1) 这个点,但不包含 (x2,y2)(x2, y2)(x2,y2),请计算出贝西总共在多少座山上漫步了。
x1,y1,x2,y2≤109x1,y1,x2,y2\le 10^9x1,y1,x2,y2≤109。
思路
因为线段没有交点,所以漫步路径是唯一的。走完一座山之后,将会下到哪座山是固定的。
有线段想用李超线段树。但是走到一座山的终点的时候,无法确定具体哪一座山峰:可能头顶有一座,哪怕没有李超树也只能返回终点的 yyy。删线段?不会捏。
本质还是模拟漫步过程。我们记下每座山的起点终点信息,每到一座山的起点就把山加进 set 里(set 支持 O(logn)O(\log n)O(logn)查询和删除)。记录当前登山编号 nownownow,初始时 now=1now=1now=1。
我们每走到山 nownownow 的终点,就要下山。走完一座山之后,将会下到哪座山是固定的,即每座山的“前继”是固定的,我们想要 set 的指针前移就能走到对应的山。
首先,若遍历到一个终点不是 nownownow 的终点,就应该把当前点所在山删掉,以免影响下山。其次,我们想要找到一个排序规则,使 set 按照这个顺序将山峰从高到低排好序。
排序规则:比较 a,ba,ba,b 的上下关系,可以借助二者终点连线 ccc 来比较,如下图已清晰说明。
-
a.xb<b.xba.xb<b.xba.xb<b.xb 时:
- 当 kb<kck_b<k_ckb<kc 时,a<ba<ba<b(如 a1,b,c1a1,b,c1a1,b,c1);
- 反之 kc<kbk_c<k_bkc<kb 时,b<ab<ab<a,aaa 可以落到 bbb(如 a2,b,c2a2,b,c2a2,b,c2)。
不用 a,ca,ca,c 比较的反例,如 a2′a2'a2′ 所示。
-
a.xb>b.xba.xb>b.xba.xb>b.xb 时:
- 当 kc<kak_c<k_akc<ka 时,a<ba<ba<b(如 a1,b,c1a1,b,c1a1,b,c1);
- 反之 ka<kck_a<k_cka<kc 时,b<ab<ab<a,bbb 可以落到 aaa(如 a2,b,c2a2,b,c2a2,b,c2)。
不用 a,ba,ba,b 比较的反例,如 b′b'b′ 所示。
dd slope(dd xa,dd ya,dd xb,dd yb)
{return (ya-yb)/(xa-xb);
}
bool operator < (hill a,hill b)//a<b,b在a上
{dd ka=slope(a.xa,a.ya,a.xb,a.yb);dd kb=slope(b.xa,b.ya,b.xb,b.yb);dd kc=slope(a.xb,a.yb,b.xb,b.yb);if(a.xb<b.xb)return kb<kc;return kc<ka;
}
在 set 上获取直接前继,可以用指针移动。
储存起点和终点,记得开 222 倍空间。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define dd double
const ll N=2e5+9;
ll n;
struct hill
{dd xa,ya,xb,yb;ll id;
}b[N];
dd slope(dd xa,dd ya,dd xb,dd yb)
{return (ya-yb)/(xa-xb);
}
bool operator < (hill a,hill b)//a<b,b在a上
{dd ka=slope(a.xa,a.ya,a.xb,a.yb);dd kb=slope(b.xa,b.ya,b.xb,b.yb);dd kc=slope(a.xb,a.yb,b.xb,b.yb);if(a.xb<b.xb)return kb<kc;return kc<ka;
}
set<hill>S;
struct point
{dd x,y;ll id;
}P[N<<1];
bool cmp(point x,point y)
{if(x.x!=y.x)return x.x<y.x;return x.y<y.y;
}
int main()
{scanf("%lld",&n);for(int i=1;i<=n;i++){dd xa,ya,xb,yb;scanf("%lf%lf%lf%lf",&xa,&ya,&xb,&yb);b[i]=(hill){xa,ya,xb,yb,i};P[i*2-1]=(point){xa,ya,i};P[i*2]=(point){xb,yb,i};}S.insert(b[1]);sort(P+1,P+2*n+1,cmp);ll ans=1,now=1;for(int i=2;i<=n*2;i++){ll id=P[i].id;if(b[id].xa==P[i].x)//起点{S.insert(b[id]);continue;}if(now!=id)S.erase(b[id]);//没走到遍历的这座山else {set<hill>::iterator B=S.find(b[id]);//走到终点了 if(B==S.begin())break;//无山可走B--,now=B->id;//下山 ans++;}}printf("%lld",ans);return 0;
}