1 条题解

  • 0
    @ 2025-8-24 22:52:38

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar Siegerkranz_2735
    老保转神|互关|原名2735

    搬运于2025-08-24 22:52:38,当前版本为作者最后更新于2024-07-10 17:08:34,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    首先可以尝试一下,可以发现答案只有三种:0,1,20,1,2。但是不知道应该怎么证明。下面我们分别来讨论几种情况:

    两个墙没有相交(包括有部分重合):

    显然他最多拐一次,只需要判断线段 abab 是否经过墙就可以了。

    两个墙相交:

    先看这个:对于一个点和一个墙,绿色部分是不能走的。

    所以两堵墙不能走的区域就是这样的:

    可以发现,只要两个点可以到达的区域有交集就只拐一次就可以了,否则就需要拐两次。

    这两个区域有交集可以转化为 aabb 分别与同侧的墙的端点的射线是否有交点,具体的话可以看图:

    具体可以这样,先判断 aabb 分别在两个线段分割的四个区域的那个部分,然后枚举 aabb 连对应区域的墙的两个点射线有没有交点,有就是 11,没有就是 22

    所以判断的顺序可以是这样:

    • abab 线段上正好有墙的一头(c,d,e,fc,d,e,f):输出 11
    • 线段ab不经过线段cd和线段ef :输出 00
    • 两个墙不相交:输出 11
    • 如果两个点可以到达的区域有交集:输出 11
    • 否则输出 22

    思路已经很清楚了,还有一些细节的实现:

    先给几个前置的知识:

    • $\text{sng}(x) = \begin{cases} -1 & x<0 \\ 0 & x=0 \\ 1 & x>0 \end{cases}$
    int sgn(__int128_t x){return x==0?0:(x<0?-1:1);}//sgn函数,只取符号 
    
    • 已知 A(x1,y1),B(x2,y2)A(x_1,y_1),B(x_2,y_2) 那么 AB=(x2x1,y2y1)\vec{AB}=(x_2-x_1,y_2-y_1)
    struct point{
    	__int128_t x,y;
    	point operator -(const point & p)const{return {x-p.x,y-p.y};}//求向量 
    }
    
    • 已知 AB=(x1,y1),CD=(x2,y2)\vec{AB}=(x_1,y_1),\vec{CD}=(x_2,y_2) 那么 AB×AC=x1y2x2y1\vec{AB}\times \vec{AC}=x_1\cdot y_2-x_2\cdot y_1
    __int128_t cross(point a,point b){return a.x*b.y-a.y*b.x;}//向量叉乘 
    
    • 向量叉乘几何意义是:AB×AC\vec{AB}\times \vec{AC} 的结果为负值,表明 B,CB,C 两点是按顺时针方向移动;AB×AC\vec{AB}\times \vec{AC}的结果为正值,表明 B,CB,C 两点是按逆时针方向移动。
    int F(point a,point b,point c){return sgn(cross(b-a,c-a));}//判断c在ab的哪一侧 
    

    怎么判断两个线段是否相交

    若两线段 a,ba,b 相交,bb 线段的两个端点一定分布在 aa 线段所在直线两侧,同时 aa 线段的两个端点一定分布在 bb 线段所在直线两侧。我们可以直接判断一条线段的两个端点相对于另一线段所在直线的位置关系。

    bool jiao(point a,point b,point c,point d){
    	if(F(a,b,c)*F(a,b,d)>=0)return 0;//cd在ab同侧 
    	if(F(c,d,a)*F(c,d,b)>=0)return 0;//ab在cd同侧 
    	return 1;//ab与cd相交 
    }
    

    两个射线是否相交

    提前在给参数的时候把射线的方向规定好就行了,最后返回那部分那个正负必须这样规定,不然交点会在射线另一侧。

    bool jiao2(point a,point b,point c,point d){
    	int sg=sgn(cross(b-a,d-c));//向量ab和cd叉乘 
    	if(sg==0)return 0;//平行 
    	return F(c,d,a)==sg && F(a,b,c)==-sg;//类似于零点存在性的证明,一个在他下面一个在他上面,而且连续所以相交。
    }
    

    怎么判断一个点是否在一个线段上

    首先要保证点的 xx 在线段的定义域,yy 在线段的值域上,并且点在线段所在的直线上就可以保证点在线段上。

    bool onLine(point a,point b,point c){//判断c是否在线段ab上 
    	if(c.x<min(a.x,b.x)||c.x>max(a.x,b.x))return 0;//c的x属于线段ab的x的范围 
    	if(c.y<min(a.y,b.y)||c.y>max(a.y,b.y))return 0;//c的y属于线段ab的y的范围 
    	return cross(c-a,c-b)==0;//c在直线ab上 
    }
    

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    struct point{
    	__int128_t x,y;
    	point operator -(const point & p)const{return {x-p.x,y-p.y};}//求向量 
    }a,b,c,d,e,f;
    void read(point &a){//读入 
    	int x,y;
    	scanf("%d%d",&x,&y);
    	a.x=x,a.y=y;
    }
    __int128_t cross(point a,point b){return a.x*b.y-a.y*b.x;}//向量叉乘 
    int sgn(__int128_t x){return x==0?0:(x<0?-1:1);}//sgn函数,只取符号 
    int F(point a,point b,point c){return sgn(cross(b-a,c-a));}//判断c在ab的哪一侧 
    bool jiao(point a,point b,point c,point d){
    	if(F(a,b,c)*F(a,b,d)>=0)return 0;//cd在ab同侧 
    	if(F(c,d,a)*F(c,d,b)>=0)return 0;//ab在cd同侧 
    	return 1;//ab与cd相交 
    }
    bool jiao2(point a,point b,point c,point d){
    	int sg=sgn(cross(b-a,d-c));//向量ab和cd叉乘 
    	if(sg==0)return 0;//平行 
    	return F(c,d,a)==sg && F(a,b,c)==-sg;//类似于零点存在性的证明,一个在他下面一个在他上面,而且连续所以相交 
    }
    bool onLine(point a,point b,point c){//判断c是否在线段ab上 
    	if(c.x<min(a.x,b.x)||c.x>max(a.x,b.x))return 0;//c的x属于线段ab的x的范围 
    	if(c.y<min(a.y,b.y)||c.y>max(a.y,b.y))return 0;//c的y属于线段ab的y的范围 
    	return cross(c-a,c-b)==0;//c在直线ab上 
    }
    int work(){
    	vector<point>X,Y;
    	if(onLine(a,b,c)||onLine(a,b,d)||onLine(a,b,e)||onLine(a,b,f))return 1;//ab线段上正好有墙的一头 
    	if(!jiao(a,b,c,d)&&!jiao(a,b,e,f))return 0;//线段ab不经过线段cd和线段ef 
    	if(!jiao(c,d,e,f))return 1;//两个墙不相交 
    	for(point p:{c,d}){//判断a和b分别在两个线段分割的四个区域的那个部分
    		if(!jiao(a,p,e,f))X.push_back(p);
    		if(!jiao(b,p,e,f))Y.push_back(p);
    	}
    	for(point p:{e,f}){
    		if(!jiao(a,p,c,d))X.push_back(p);
    		if(!jiao(b,p,c,d))Y.push_back(p);
    	}//看射线是否相交
    	for(auto i:X)for(auto j:Y)if(jiao2(a,i,b,j))return 1;
    	return 2;
    }
    int main(){
    	int T;
    	for(scanf("%d",&T);T--;printf("%d\n",work()))read(a),read(b),read(c),read(d),read(e),read(f);
    	return 0;
    }
    

    附记:从一月末就开始写这篇题解了,拖到现在才写完,2735你不能这样颓废了。

    • 1

    信息

    ID
    9407
    时间
    4000ms
    内存
    1024MiB
    难度
    6
    标签
    递交数
    0
    已通过
    0
    上传者