「专题总结」半平面交(6/8completed)
还是题都没做完就来写一部分题解了,因为最近很忙(其实是效率太低),所以可能一时半会回不来这个专题。
为了防止什么都没剩下忘干净,于是乎瞎写个题解记录一下。。。
还是一如既往的不擅长几何。
刚开始我看到半平面交我还以为是用来解决三维计算几何问题的,吓一跳。
半平面不用多说,就是一条直线把一个二维平面分成两半,其中的一半。
半平面交其实很好理解,就是同一个二维平面中若干个半平面相交的部分。
维护的方法其实挺草率的,和凸包有不少互通的地方,只不过因为封闭所以是双端队列。
凸包偏向于点,半平面交偏向于线。
通常的方法是维护一大堆向量,默认向量的左侧是需要的半平面。
向量的加入顺序通常还是极角,用库函数中的atan2计算。(不要每次都现场算,很慢)
只要队首或队尾的连续两个元素的交点在当前要加入直线的右侧,就把它弹掉就可以。
又因为它是封闭的,所以在全部元素加入完成后你还要认为你把队首尾相连了,即用队尾弹队首,队首弹队尾。
原理不明,实践证明,要先弹队尾后弹队首,否则会出锅。
半平面交从含义上理解是维护了一个凸多边形。但是其实半平面交也可以是无穷平面或其它图形。
处理无穷平面,我们通常在最外侧手动加一个坐标为inf的框(4条向量),这样它最后一定不是无穷平面。
对于最后剩下的依旧不是凸多边形的话,那么队列里的元素一定不超过2个(不封闭,弹队列还弹不掉)
这时候你可以认为半平面交不存在,面积为0。
需要比较熟练的运用叉积来计算方向,交点和面积。
另外这个专题还极容易炸精。。。体验极差。
半平面交的应用?
一方面是从定义上直接使用。
另一方面是它可以求出多边形的核,即多边形中可以看见多边形所有位置的点的集合。
再一方面就是,因为直线是二元一次方程的解,半平面则是二元一次不等式的解集。于是半平面交就是二元一次不等式的解集。
赛车:
$Description:$
这里有一辆赛车比赛正在进行,赛场上一共有N辆车,分别称为个g1,g2……gn。赛道是一条无限长的直线。最初,gi位于距离起跑线前进ki的位置。比赛开始后,车辆gi将会以vi单位每秒的恒定速度行驶。在这个比赛过程中,如果一辆赛车曾经处于领跑位置的话(即没有其他的赛车跑在他的前面),这辆赛车最后就可以得奖,而且比赛过程中不用担心相撞的问题。现在给出所有赛车的起始位置和速度,你的任务就是算出那些赛车将会得奖。
刚从凸包转半平面交时做的题不明所以。
大概可以理解为维护直线的凸包。。。
而且还不封闭所以只需要单向加边。。。和单调栈一模一样。就是凸包了啊。。。
1 #include<bits/stdc++.h> 2 using namespace std; 3 struct L{int k,b,o;friend bool operator<(L a,L b){return a.k<b.k||(a.k==b.k&&a.b<b.b);}}l[10001]; 4 vector<int>v[10001]; 5 int s[10001],tp,an,ans[10001]; 6 int main(){ 7 int n;scanf("%d",&n); 8 for(int i=1;i<=n;++i)scanf("%d",&l[i].b),l[i].o=i; 9 for(int i=1;i<=n;++i)scanf("%d",&l[i].k),v[i].push_back(i); 10 sort(l+1,l+1+n); 11 for(int i=1;i<=n;++i){ 12 while(tp&&l[s[tp]].k==l[i].k&&l[s[tp]].b<l[i].b)tp--; 13 while(tp&&l[s[tp]].b<l[i].b)tp--; 14 if(l[s[tp]].k==l[i].k&&l[s[tp]].b==l[i].b){v[l[s[tp]].o].push_back(l[i].o);continue;} 15 while(tp>1&&1ll*(l[s[tp-1]].b-l[i].b)*(l[s[tp]].k-l[i].k)<1ll*(l[s[tp]].b-l[i].b)*(l[s[tp-1]].k-l[i].k))tp--; 16 s[++tp]=i; 17 }for(int i=1;i<=tp;++i)for(int j=0;j<v[l[s[i]].o].size();++j)ans[++an]=v[l[s[i]].o][j]; 18 sort(ans+1,ans+an+1); 19 printf("%d\n",an); 20 for(int i=1;i<an;++i)printf("%d ",ans[i]);if(an)printf("%d",ans[an]); 21 }
暸望塔:
$Description:$
致力于建设全国示范和谐小村庄的H村村长dadzhi,决定在村中建立一个瞭望塔,以此加强村中的治安。我们将H村抽象为一维的轮廓。如下图所示 我们可以用一条山的上方轮廓折线(x1, y1), (x2, y2), …. (xn, yn)来描述H村的形状,这里x1 < x2 < …< xn。瞭望塔可以建造在[x1, xn]间的任意位置, 但必须满足从瞭望塔的顶端可以看到H村的任意位置。可见在不同的位置建造瞭望塔,所需要建造的高度是不同的。为了节省开支,dadzhi村长希望建造的塔高度尽可能小。请你写一个程序,帮助dadzhi村长计算塔的最小高度。
比较模板。但是图形仍然不封闭,所以还差不多可以当成凸包做。
在天上维护一个半平面交,那么天地都是一个分段一次函数,最优解当然在两者的拐点之一。
1 #include<bits/stdc++.h> 2 using namespace std; 3 const double eps=1e-8; 4 struct P{double x,y;P(){} P(double a,double b):x(a),y(b){}}rp[301],RP[301]; 5 struct L{P s,t;double A;}r[301],R[301]; 6 P operator+(P a,P b){return P(a.x+b.x,a.y+b.y);} 7 P operator-(P a,P b){return P(a.x-b.x,a.y-b.y);} 8 double X(P a,P b){return a.x*b.y-a.y*b.x;} 9 double operator*(P a,P b){return a.x*b.x+a.y*b.y;} 10 P operator*(P a,double r){return P(a.x*r,a.y*r);} 11 P v(L x){return x.t-x.s;} 12 bool cmp(L a,L b){return fabs(a.A-b.A)>eps?a.A<b.A:X(a.s-b.s,b.t-b.s)<eps;} 13 P is(L a,L b){return a.s+v(a)*(X(v(b),(a.s-b.s))/X(v(a),v(b)));} 14 int s[301],t,lc,pc;double ans=3e18; 15 void cal(P p,L l){ans=min(ans,fabs(p.y-l.s.y-v(l).y*((p.x-l.s.x)/(v(l).x))));} 16 int main(){//freopen("1.in","r",stdin); 17 int n;cin>>n; 18 for(int i=1;i<=n;++i)scanf("%lf",&rp[i].x); 19 for(int i=1;i<=n;++i)scanf("%lf",&rp[i].y); 20 for(int i=1;i<n;++i)r[i]=(L){rp[i],rp[i+1],atan2(rp[i+1].y-rp[i].y,rp[i+1].x-rp[i].x)}; 21 sort(r+1,r+n,cmp);R[lc=1]=r[1]; 22 for(int i=2;i<n;++i)if(fabs(r[i].A-R[lc].A)>eps)R[++lc]=r[i]; 23 for(int i=1;i<=lc;++i){ 24 while(t>1&&X(v(R[i]),(is(R[s[t-1]],R[s[t]])-R[i].s))<-eps)t--; 25 s[++t]=i; 26 }for(int i=1;i<t;++i)RP[++pc]=is(R[s[i]],R[s[i+1]]),R[s[i+1]].s=R[s[i]].t=RP[pc]; 27 R[s[1]].s=R[s[1]].t-v(R[s[1]])*1e9; R[s[t]].t=R[s[t]].s+v(R[s[t]])*1e9; 28 for(int i=1;i<=n;++i)for(int j=1;j<=t;++j)if(R[s[j]].s.x<rp[i].x+eps&&rp[i].x<R[s[j]].t.x+eps)cal(rp[i],R[s[j]]); 29 for(int i=1;i<t;++i)for(int j=1;j<n;++j)if(r[j].s.x<RP[i].x+eps&&RP[i].x<r[j].t.x+eps)cal(RP[i],r[j]); 30 printf("%.3lf\n",ans); 31 }
射箭:
$Description:$
沫沫最近在玩一个二维的射箭游戏,如下图 1 所示,这个游戏中的 x 轴在地面,第一象限中有一些竖直线段作为靶子,任意两个靶子都没有公共部分,也不会接触坐标轴。沫沫控制一个位于(0,0)的弓箭手,可以朝 0 至 90?中的任意角度(不包括 0度和 90度),以任意大小的力量射出带有穿透能力的光之箭。由于游戏中没有空气阻力,并且光之箭没有箭身,箭的轨迹会是一条标准的抛物线,被轨迹穿过的所有靶子都认为被沫沫射中了,包括那些 只有端点被射中的靶子。这个游戏有多种模式,其中沫沫最喜欢的是闯关模式。在闯关模式中,第一关只有一个靶 子,射中这个靶子即可进入第二关,这时在第一关的基础上会出现另外一个靶子,若能够一箭 双雕射中这两个靶子便可进入第三关,这时会出现第三个靶子。依此类推,每过一关都会新出 现一个靶子,在第 K 关必须一箭射中前 K 关出现的所有 K 个靶子才能进入第 K+1 关,否则游戏 结束。沫沫花了很多时间在这个游戏上,却最多只能玩到第七关“七星连珠”,这让她非常困惑。 于是她设法获得了每一关出现的靶子的位置,想让你告诉她,最多能通过多少关。$n \le 100000$
二次函数。不会做。发现它一定经过原点,于是所有坐标除以$x$。变为一次函数。
和《猜数游戏》很像,「满足的条件数」这个单调性很明显,不难想到二分答案。
然后其实就是若干个二元一次方程求解集是否为空。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 211111 4 #define db long double 5 const db eps=1e-17,inf=1e18; 6 struct P{db x,y;P(){} P(db a,db b):x(a),y(b){}}; 7 struct L{P s,t;db A;int o;}li[S],f[S]; 8 P operator+(P a,P b){return P(a.x+b.x,a.y+b.y);} 9 P operator-(P a,P b){return P(a.x-b.x,a.y-b.y);} 10 P operator*(P a,db b){return P(a.x*b,a.y*b);} 11 db operator*(P a,P b){return a.x*b.x+a.y*b.y;} 12 db operator^(P a,P b){return a.x*b.y-a.y*b.x;} 13 bool cmp(L a,L b){return fabs(a.A-b.A)>eps?a.A<b.A:((a.s-b.s)^(b.t-b.s))<0;} 14 P v(L a){return a.t-a.s;} 15 P is(L a,L b){return a.s+v(a)*((v(b)^(a.s-b.s))/(v(a)^v(b)));} 16 int n,q[S],h,t,lc;db x[S],yd[S],yu[S]; 17 bool sat(L a,L b,L x){return ((v(x))^(is(a,b)-x.s))<-eps;} 18 bool chk(int M){h=1;t=0; 19 for(int i=1;i<=lc;++i)if(f[i].o<=M){ 20 while(t>h&&sat(f[q[t]],f[q[t-1]],f[i]))t--; 21 while(t>h&&sat(f[q[h]],f[q[h+1]],f[i]))h++; 22 q[++t]=i; 23 } 24 while(t>h&&sat(f[q[t]],f[q[t-1]],f[q[h]]))t--; 25 while(t>h&&sat(f[q[h]],f[q[h+1]],f[q[t]]))h++; 26 return t>h+1; 27 } 28 int main(){ 29 cin>>n;for(int i=1;i<=n;++i)scanf("%Lf%Lf%LF",&x[i],&yd[i],&yu[i]),yd[i]/=x[i],yu[i]/=x[i]; 30 int m=4,l=1,r=n,ans; 31 li[1]=(L){P(eps,0),P(eps,1),0,0}; 32 li[2]=(L){P(0,eps),P(1,eps),0,0}; 33 li[3]=(L){P(-inf,0),P(inf,-1),0,0}; 34 li[4]=(L){P(0,inf),P(-1,inf),0,0}; 35 for(int i=1;i<=n;++i) 36 li[++m]=(L){P(0,1.0L*yd[i]),P(1,1.0L*yd[i]-x[i]),0,i}, 37 li[++m]=(L){P(1,1.0L*yu[i]-x[i]),P(0,1.0L*yu[i]),0,i}; 38 for(int i=1;i<=m;++i)li[i].A=atan2(li[i].t.y-li[i].s.y,li[i].t.x-li[i].s.x); 39 sort(li+1,li+1+m,cmp);f[lc=1]=li[1]; 40 for(int i=2;i<=m;++i)if(fabs(li[i].A-f[lc].A)>eps)f[++lc]=li[i]; 41 while(l<=r)if(chk(l+r>>1))l=ans=l+r>>1,l++;else r=(l+r>>1)-1; 42 cout<<ans<<endl; 43 }
铁人双项比赛:
$Description:$
铁人双项比赛是吉林教育学院的一项传统体育项目。该项目比赛由长跑和骑自行车组成,参赛选手必须先完成k公里的长跑,然后完成r公里的骑车,才能到达终点。每个参赛选手所擅长的项目不同,有的擅长长跑,有的擅长骑车。如果总赛程s=k+r一定,那么K越大,对擅长长跑的选手越有利;k越小,对擅长骑车的选手越有利。
现在给定总赛程s,以及每个选手长跑和骑车的平均速度,请你求出对于某个指定的选手最有利的k和r。所谓最有利,是指选择了这个k和r后,该选手可以获得冠军,且领先第2名尽量地多。$n \le 100$
如果这道题你当成半平面交做那么下边这题就会很恶心吧。。。
经典的三分题目啊。。。三分长跑在总赛程中的比例就行。。。别忘了单位换算
1 #include<bits/stdc++.h> 2 using namespace std; 3 int n;long double x[102],y[102],s,l=0,r,md; 4 long double cal(long double t){long double mn=1e18L; 5 for(int i=1;i<n;++i)mn=min(mn,t/x[i]+(s-t)/y[i]); 6 return mn-t/x[n]-(s-t)/y[n]; 7 } 8 int main(){ 9 cin>>s>>n;for(int i=1;i<=n;++i)cin>>x[i]>>y[i]; 10 r=s;while(md=(l+r)/2,r-l>1e-7L)if(cal(md+1e-8L)>cal(md)+1e-15L)l=md;else r=md; 11 if(cal(l)<0)puts("NO");else printf("%.2Lf %.2Lf %.0Lf\n",l,s-l,cal(l)*3600); 12 }
Saber VS Lancer:
$Description:$
铁人三项是一种运动项目,和字面意思一样,是让铁做的人(?)去做三个项目,必须连续完成,而且全程讲求速度。第一项是游泳,第二项是骑自行车,第三项是跑步。现在所有选手的三个项目的速度都是已知的。但是这次比赛中,裁判可以任意选择每一个项目的路程长度(假设没有一项长度为0)。但是这样显然会影响比赛排名……有时她会按某种方式选择,使得一些个别的选手能赢得竞赛。$n\le 100$
这道题才轮的到半平面交出场吧。设总路径长度为1。
设其中两项分别为$x$,$y$,那么剩下一个就是$1-x-y$。根据这个就可以列出每个选手用时的表达式。
$n$很小,可以对每个选手单独考虑。将每个选手与其它选手比较列出若干不等式,形式很半平面交,查看是否有解即可。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define db long double 4 const db eps=1e-18L; 5 db v1[111],v2[111],v3[111];int n,m,q[111],h,t,c; 6 struct P{db x,y; P(){} P(db a,db b):x(a),y(b){}}; 7 struct L{P s,t;db A;}li[111],r[111]; 8 P operator+(P a,P b){return P(a.x+b.x,a.y+b.y);} 9 P operator-(P a,P b){return P(a.x-b.x,a.y-b.y);} 10 P operator*(P a,db b){return P(a.x*b,a.y*b);} 11 db operator*(P a,P b){return a.x*b.y-a.y*b.x;} 12 P v(L x){return x.t-x.s;} 13 bool cmp(L a,L b){return fabs(a.A-b.A)>eps?a.A<b.A:(a.s-b.s)*v(b)<0;} 14 P is(L a,L b){return a.s+v(a)*((v(b)*(a.s-b.s))/(v(a)*v(b)));} 15 bool sat(L a,L b,L x){return t>h&&v(x)*(is(a,b)-x.s)<eps;} 16 int main(){//freopen("triath24.in","r",stdin);freopen("my.out","w",stdout); 17 cin>>n; 18 for(int i=1;i<=n;++i)cin>>v1[i]>>v2[i]>>v3[i],v1[i]=1/v1[i],v2[i]=1/v2[i],v3[i]=1/v3[i]; 19 for(int i=1;i<=n;++i){ 20 m=3;h=2;t=1;li[1]={P(0,0),P(0,-1),0};li[2]={P(0,0),P(1,0),0};li[3]={P(1,0),P(0,1),0}; 21 for(int j=1;j<=n;++j)if(i!=j&&fabs(v1[i]-v1[j])+fabs(v2[i]-v2[j])+fabs(v3[i]-v3[j])<eps){puts("No");goto X;} 22 for(int j=1;j<=n;++j)if(i!=j&&v1[i]>v1[j]&&v2[i]>v2[j]&&v3[i]>v3[j]){puts("No");goto X;} 23 for(int j=1;j<=n;++j)if(i!=j){ 24 db s1=v1[i]-v3[i]+v3[j]-v1[j],s2=v2[i]-v3[i]-v2[j]+v3[j]; 25 if(fabs(s2)<eps)li[++m]=(L){P((v3[j]-v3[i])/s1,0),P((v3[j]-v3[i])/s1,s1>eps?1:-1),0}; 26 else if(s2>0)li[++m]=(L){P(1,(v1[j]-v1[i])/s2),P(0,(v3[j]-v3[i])/s2),0}; 27 else li[++m]=(L){P(0,(v3[j]-v3[i])/s2),P(1,(v1[j]-v1[i])/s2),0}; 28 } 29 for(int j=1;j<=m;++j)li[j].A=atan2(li[j].t.y-li[j].s.y,li[j].t.x-li[j].s.x); 30 sort(li+1,li+1+m,cmp);r[c=1]=li[1];//cout<<li[1].s.x<<' '<<li[1].s.y<<' '<<li[1].t.x<<' '<<li[1].t.y<<' '<<li[1].A<<endl; 31 for(int j=2;j<=m;++j)if(fabs(li[j].A-li[j-1].A)>eps)r[++c]=li[j];//else printf("%.10Lf %.10Lf\n",li[j].A,r[c].A); 32 for(int j=1;j<=c;++j){ 33 while(sat(r[q[t]],r[q[t-1]],r[j]))t--; 34 while(sat(r[q[h]],r[q[h+1]],r[j]))h++; 35 q[++t]=j; 36 } 37 while(sat(r[q[t]],r[q[t-1]],r[q[h]]))t--; 38 while(sat(r[q[h]],r[q[h+1]],r[q[t]]))h++; 39 puts(t>h+1?"Yes":"No");X:;//cout<<i<<' '<<c<<endl; 40 } 41 }
小凸想跑步:
$Description:$
小凸晚上喜欢到操场跑步,今天他跑完两圈之后,他玩起了这样一个游戏。
操场是个凸n边形,个顶点按照逆时针从0编号。现在小凸随机站在操场中的某个位置,标记为P点。将P点与顶点各连一条边,形成n个三角形。
如果这时P点,0号点,1号点形成的三角形的面积是n个三角形中最小的一个,小凸则认为这是一次正确站位。
现在小凸想知道他一次站位正确的概率是多少。
概率?肯定是不存在的。是面积比。
原多边形的面积拆成三角形叉积算一算就出来了。
然后要求这个三角形面积比其它的小。。。线段是确定的,三角形的面积是与横纵坐标相关的不等式。。。
又是解不等式组。。。三角形的面积直接用底乘高表示,其实不是很麻烦。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 222222 4 #define db long double 5 const db eps=1e-8L; 6 int n,m,lc,q[S],h=1,t;db x[S],y[S],tA,aA; 7 struct P{db x,y;P(){} P(db a,db b):x(a),y(b){}}; 8 struct L{P s,t;db A;}li[S],r[S]; 9 P operator+(P a,P b){return P(a.x+b.x,a.y+b.y);} 10 P operator-(P a,P b){return P(a.x-b.x,a.y-b.y);} 11 P operator*(P a,db k){return P(a.x*k,a.y*k);} 12 db operator*(P a,P b){return a.x*b.y-a.y*b.x;} 13 P v(L x){return x.t-x.s;} 14 bool cmp(L a,L b){return fabs(a.A-b.A)>eps?a.A<b.A:(a.s-b.s)*v(b)<0;} 15 P is(L a,L b){return a.s+v(a)*(((a.s-b.s)*v(b))/(v(b)*v(a)));} 16 bool sat(int a,int b,int x){return t>h&&v(r[x])*(is(r[a],r[b])-r[x].s)<eps;} 17 db cal(P x,L l){return fabs((l.s-x)*v(l));} 18 void pt(L l){cout<<'('<<l.s.x<<','<<l.s.y<<") ("<<l.t.x<<','<<l.t.y<<")\n";} 19 int main(){//freopen("1.in","r",stdin); 20 scanf("%d",&n); for(int i=1;i<=n;++i)scanf("%Lf%Lf",&x[i],&y[i]); 21 x[n+1]=x[1];y[n+1]=y[1]; 22 for(int i=1;i<=n;++i)li[++m]=(L){P(x[i],y[i]),P(x[i+1],y[i+1]),0}; 23 for(int i=2;i<n;++i)aA+=cal(P(x[1],y[1]),li[i]); 24 db A=y[1]-y[2],B=x[2]-x[1],C=x[1]*y[2]-x[2]*y[1]; 25 for(int i=2;i<=n;++i){ 26 db x1=x[i],y1=y[i],x2=x[i+1],y2=y[i+1],a=y1-y2,b=x2-x1,c=x1*y2-x2*y1,sa=A-a,sb=B-b,sc=c-C,ea=sc/sa,eb=sc/sb; 27 //Ax+By+C<ax+by+c (A-a)x+(B-b)y<c-C sa*x+sb*y<sc 28 if(fabs(sa)+fabs(sb)<eps&&sc>0)continue; 29 if(fabs(sa)+fabs(sb)<eps)return puts("0.0000"),0; 30 if(fabs(sb)<eps&&sa<0)li[++m]=(L){P(ea,0),P(ea,-1),0}; 31 else if(fabs(sb)<eps)li[++m]=(L){P(ea,0),P(ea,1),0}; 32 else if(sb<0)li[++m]=(L){P(0,eb),P(1,(sc-sa)/sb),0}; 33 else li[++m]=(L){P(1,(sc-sa)/sb),P(0,eb),0}; 34 } 35 for(int i=1;i<=m;++i)li[i].A=atan2(li[i].t.y-li[i].s.y,li[i].t.x-li[i].s.x); 36 sort(li+1,li+m+1,cmp); r[lc=1]=li[1]; 37 for(int i=2;i<=m;++i)if(li[i].A-li[i-1].A>eps)r[++lc]=li[i];//,pt(li[i]);else cout<<"del:",pt(li[i]); 38 for(int i=1;i<=lc;++i){ while(sat(q[t-1],q[t],i))t--; while(sat(q[h],q[h+1],i))h++; q[++t]=i; } 39 while(sat(q[t-1],q[t],q[h]))t--; while(sat(q[h],q[h+1],q[t]))h++; 40 // cout<<h<<' '<<t<<endl;for(int i=h;i<=t;++i)pt(r[q[i]]);puts("---"); 41 for(int i=h;i<t;++i)r[q[i]].t=r[q[i+1]].s=is(r[q[i]],r[q[i+1]]); 42 // for(int i=h;i<=t;++i)pt(r[q[i]]); 43 P X=is(r[q[h]],r[q[t]]); 44 for(int i=2;i<t;++i)tA+=cal(X,r[q[i]]); 45 printf("%.4Lf\n",tA/aA);//cout<<h<<' '<<t<<' '<<lc<<endl; 46 }