• Codeforces Gym100543B 计算几何 凸包 线段树 二分/三分 卡常


    原文链接https://www.cnblogs.com/zhouzhendong/p/CF-Gym100543B.html

    题目传送门 - CF-Gym100543B

    题意

      给定一个折线图,对于每一条折线,问沿着这条折线往右看第一个看到的线段的编号(如果视线恰好看到上端点,则当没看见)

      放张图片助于理解:

      

      折线图用 $n$ 个点来描述。

      $nleq 100000, 坐标范围:(x,y)|0leq x,yleq 10^9$

    题解

      这题好妙啊。

      首先一个结论:如果射线与一段区间的点形成的上凸壳相交,那么他一定与这段区间内的折线段相交。

      我们只需要建个线段树,所有节点上建一个当前节点所表示的区间内的点构成的上凸壳,然后每次 $O(log^2 n)$ 询问即可。

      如何 $O(log^2 n)$ 询问?

      首先,线段树一只 $log$ 。

      我们需要支持的是一只 $log$ 判断射线是否与上凸壳相交。

      显然原线段与上凸壳的点的叉积是一个单峰函数。(根据叉积的定义,平行四边形的低不变,高为单峰函数,故面积也为单峰函数)

      于是显然可以三分搞定。

      但是被卡常数了。

      于是 foreverpiano 告诉了我一种巧妙的二分做法。

      (这里求叉积的点依次是线段左侧点,线段右侧点,当前点)

      对于每一次的 $mid$ ,我们看一看 原线段与凸壳上面的第 $mid$ 和 $mid+1$ 个点的叉积大小,分别记为 $v1$ 和 $v2$。

      如果 $v1>v2$ 则令 $R=mid-1$ 否则令 $L=mid+1$ 。

      注意一旦有 $v1>0$ 或者 $v2>0$ 就可以判断一定相交了。如果这个时候不return,则可能会有漏算。

      然后区间极小的时候暴力判。

      然后常数小了好多。

    代码

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long LL;
    const int N=100005;
    int T,n;
    struct Point{
    	int x,y;
    	Point(){}
    	Point(int _x,int _y){
    		x=_x,y=_y;
    	}
    }p[N],P[N];
    vector <int> s[N<<3];
    LL cross(Point a,Point b,Point c){
    	return 1LL*(b.x-a.x)*(c.y-a.y)-1LL*(c.x-a.x)*(b.y-a.y);
    }
    int read(){
    	char ch=getchar();
    	int x=0;
    	while (!isdigit(ch))
    		ch=getchar();
    	while (isdigit(ch))
    		x=(x<<1)+(x<<3)+ch-48,ch=getchar();
    	return x;
    }
    int st[N],top;
    void Get_convex(vector <int> &s,int L,int R){
    	st[top=1]=R,st[++top]=R-1;
    	for (int i=R-2;i>=L;i--){
    		while (top>1&&cross(p[st[top-1]],p[st[top]],p[i])<=0)
    			top--;
    		st[++top]=i;
    	}
    	s.clear();
    	for (int i=top;i>0;i--)
    		s.push_back(st[i]);
    }
    void build(int rt,int L,int R){
    	if (L==R){
    		s[rt].clear();
    		s[rt].push_back(L);
    		return;
    	}
    	Get_convex(s[rt],L,R);
    	int mid=(L+R)>>1,ls=rt<<1,rs=ls|1;
    	build(ls,L,mid);
    	build(rs,mid+1,R);
    }
    int Query(Point a,Point b,vector <int> &s){
    	int L=0,R=s.size()-1,mid;
    	while (L+3<=R){
    		mid=(L+R)>>1;
    		LL x=cross(a,b,p[s[mid]]),y=cross(a,b,p[s[mid+1]]);
    		if (x>0||y>0)
    			return 1;
    		if (x>y)
    			R=mid-1;
    		else
    			L=mid+1;
    	}
    	int now=-1;
    	for (int i=L;i<=R;i++)
    		if (cross(a,b,p[s[i]])>0)
    			return 1;
    	return 0;
    }
    int Query(int rt,int L,int R,int xL,int xR){
    	if (xL>xR||L>xR||R<xL||!Query(p[xL-2],p[xL-1],s[rt]))
    		return 0;
    	if (L==R)
    		return L-1;
    	int mid=(L+R)>>1,ls=rt<<1,rs=ls|1;
    	int now=Query(ls,L,mid,xL,xR);
    	return now?now:Query(rs,mid+1,R,xL,xR);
    }
    int main(){
    	T=read();
    	while (T--){
    		n=read();
    		for (int i=1;i<=n;i++)
    			p[i].x=read(),p[i].y=read();
    		build(1,1,n);
    		for (int i=1;i<n;i++){
    			cout << Query(1,1,n,i+2,n);
    			if (i<n-1)
    				putchar(' ');
    		}
    		puts("");
    	}
    	return 0;
    }
    

      

  • 相关阅读:
    uniapp解决图形验证码问题及arraybuffer二进制转base64格式图片
    uni-app图片上传接口联调
    Redis与Mysql双写一致性方案解析(转载)
    python 3 for循环倒序、一组数据参数解包
    python使用for循环打印直角三角形、菱形、乘法口诀,1至100的和、奇数和、偶数和
    使用jmeter做接口测试,简单实例
    python发送无参数get请求
    python的第三方库unittestreport 详细功能使用文档(V1.1.1)-转载
    python 3 发送邮件(转载)
    python使用apscheduler执行定时任务时报错:Run time of job "pr (trigger: cron[minute='25'], next run at: 2021-05-05 22:25:00 CST)" was missed by 0:00:01.185258
  • 原文地址:https://www.cnblogs.com/zhouzhendong/p/CF-Gym100543B.html
Copyright © 2020-2023  润新知