• 【洛谷4758】[CERC2014] Mountainous landscape(线段树维护凸壳)


    题目链接

    • 给定二维平面内一条由 \(n\) 个折点构成的折线,保证折点横坐标递增。
    • 对于每条折线段 \(P_iP_{i+1}\),找到大于 \(i\) 的最小的 \(j\) ,满足 \(P_jP_{j+1}\) 上存在一点严格在射线 \(P_iP_{i+1}\) 上方。
    • \(2\le n\le10^5\)

    二分答案

    容易想到二分答案,这样每次就只需检验某个区间内所有点是否都在 \(P_iP_{i+1}\) 下方。

    假设当前的答案区间是 \([l,r]\),如果把 \(P_i,P_{i+1}\)\(P_l,P_{l+1},\cdots,P_{r+1}\) 放在一起,则所有点都在 \(P_iP_{i+1}\) 下方充要于 \(P_iP_{i+1}\)上凸壳 中。

    \(P_iP_{i+1}\) 在上凸壳中,充要于除去 \(P_i,P_{i+1}\) 之后,剩下的 \(P_l,P_{l+1},\cdots,P_{r+1}\) 这些点构成的上凸壳在 \(P_iP_{i+1}\) 下方。

    线段树维护凸壳

    因为点是不变的,我们可以对于线段树上每个区间 \([l,r]\),预处理出 \(P_l,P_{l+1},\cdots,P_{r+1}\) 构成的上凸壳。

    然后二分就可以转化成线段树上二分,每次三分一下,看看这个区间中的点构成的上凸壳中是否有点在当前询问的 \(P_iP_{i+1}\) 上方即可。

    代码:\(O(n\log^2n)\)

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Rg register
    #define RI Rg int
    #define Cn const
    #define CI Cn int&
    #define I inline
    #define W while
    #define N 100000
    using namespace std;
    int n;struct P
    {
    	int x,y;I P(CI a=0,CI b=0):x(a),y(b){}
    	I P operator + (Cn P& o) Cn {return P(x+o.x,y+o.y);}I P operator - (Cn P& o) Cn {return P(x-o.x,y-o.y);}
    	I long long operator ^ (Cn P& o) Cn {return 1LL*x*o.y-1LL*y*o.x;}
    }p[N+5];
    namespace FastIO
    {
    	#define FS 100000
    	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
    	#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
    	int OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
    	I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
    	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
    	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
    	Tp I void write(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);pc(' ');}I void ed() {pc('0'),pc('\n');}
    }using namespace FastIO;
    class SegmentTree
    {
    	private:
    		#define PT CI l=1,CI r=n-1,CI rt=1
    		#define LT l,mid,rt<<1
    		#define RT mid+1,r,rt<<1|1
    		int ct[N<<2];vector<P> G[N<<2];
    	public:
    		I void Bd(PT)//预处理
    		{
    			RI& T=ct[rt];vector<P>& g=G[rt];T=-1,g.clear();
    			for(RI i=l;i<=r+1;++i) {W(T>0&&((p[i]-g[T])^(g[T]-g[T-1]))<=0) --T,g.pop_back();++T,g.push_back(p[i]);}//[l,r+1]中的点构成的上凸壳
    			if(l==r) return;RI mid=l+r>>1;Bd(LT),Bd(RT); 
    		}
    		I int Q(CI x,PT)//线段树上二分
    		{
    			RI mid=l+r>>1,L=0,R=ct[rt],M1,M2;long long t1,t2;if(x>=mid) return Q(x,RT);//若x+1在右区间,直接去右区间
    			W(R-L>2) t1=(p[x+1]-p[x])^(G[rt][M1=L+(R-L)/3]-p[x]),t2=(p[x+1]-p[x])^(G[rt][M2=M1+(R-L)/3]-p[x]),t1>t2?R=M2:L=M1;//三分是否有点在当前线段上方
    			RI i;for(i=L;i<=R;++i) if(((p[x+1]-p[x])^(G[rt][i]-p[x]))>0) break;if(i>R) return 0;//如果没有返回0
    			if(l==r) return l;RI t=Q(x,LT);return t?t:Q(x,RT);//优先尝试左子树,失败则去右子树(因为找不到会直接返回,复杂度正确)
    		}
    }S;
    int main()
    {
    	RI Tt,i;read(Tt);W(Tt--) {for(read(n),i=1;i<=n;++i) read(p[i].x,p[i].y);S.Bd();for(i=1;i<n-1;++i) write(S.Q(i));ed();}return clear(),0;
    }
    
  • 相关阅读:
    985的方格难题
    POJ 3264 区间最大最小值Sparse_Table算法
    oracle中to_date详细用法示例(oracle日期格式转换)
    PLSQL基础知识-图片
    oracle-查询-时间条件查询
    oracle基础函数--decode
    PLSQL基础学习-文字
    python3 MD5
    CentOS7关闭防火墙方法
    CentOS 7下源码安装MySQL 5.6
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu4758.html
Copyright © 2020-2023  润新知