计算几何
(一)半平面交:化出一些一元二次不等式,然后就可以求一些半平面交来找合法解了。
bzoj1007 水平可见直线
题目大意:给定一些直线,求从无限高处可以看到那些直线。
思路:对于每一条直线都是一个半平面,然后加上无穷远的半平面之后求半平面交,用到的就是。这里无穷远的半平面可以赋成1e100防止错误。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define maxnode 50005 #define inf 1e100 #define eps 1e-20 using namespace std; struct point{double x,y;}p[maxnode]={0}; struct vector{double x,y;}; int n,que[maxnode]={0}; bool use[maxnode]={false}; double dis(double x,double y){return sqrt(x*x+y*y);} point operator + (point a,vector b){return (point){a.x+b.x,a.y+b.y};} vector operator - (point a,point b){return (vector){a.x-b.x,a.y-b.y};} vector operator * (vector a,double b){return (vector){a.x*b,a.y*b};} struct line{ point p;vector v; double ang;int po; void init(double k,double b) { p=(point){0.0,b*1.0}; double dd=dis(1.0,k*1.0); v.x=1.0/dd;v.y=k*1.0/dd; ang=atan2(v.y,v.x); } bool operator < (const line &a) const{return ang<a.ang;} }ai[maxnode]; double cross(vector a,vector b){return a.x*b.y*1.0-a.y*b.x*1.0;} bool onleft(line a,point b){return cross(a.v,b-a.p)>0;} point getjiao(line a,line b) { vector u=a.p-b.p; double t=cross(b.v,u)/cross(a.v,b.v); return a.p+a.v*t; } void get() { int head,tail,i; ai[++n].p=(point){inf,0};ai[n].v=(vector){0,1}; ai[n].po=0;ai[n].ang=atan2(1,0); ai[++n].p=(point){0,inf};ai[n].v=(vector){-1,0}; ai[n].po=0;ai[n].ang=atan2(0,-1); ai[++n].p=(point){-inf,0};ai[n].v=(vector){0,-1}; ai[n].po=0;ai[n].ang=atan2(-1,0); ai[++n].p=(point){0,-inf};ai[n].v=(vector){1,0}; ai[n].po=0;ai[n].ang=atan2(0,1); sort(ai+1,ai+n+1);que[head=tail=1]=1; for (i=2;i<=n;++i) { while(head<tail&& !onleft(ai[i],p[tail-1])) --tail; while(head<tail&& !onleft(ai[i],p[head])) ++head; que[++tail]=i; if (fabs(cross(ai[que[tail]].v,ai[que[tail-1]].v))<eps) { --tail; if (onleft(ai[que[tail]],ai[i].p)) que[tail]=i; } if (head<tail) p[tail-1]=getjiao(ai[que[tail-1]],ai[que[tail]]); } while(head<tail&&!onleft(ai[que[head]],p[tail-1])) --tail; for (i=head;i<=tail;++i) use[ai[que[i]].po]=true; } int main() { int i,j;double k,b; scanf("%d",&n); for (i=1;i<=n;++i) { scanf("%lf%lf",&k,&b); ai[i].init(k,b);ai[i].po=i; } get(); for (i=1;i<=n;++i) if (use[i]) printf("%d ",i); printf("\n"); }
bzoj2732 射箭
题目大意:在第一象限给定一些竖直线段,一共n轮,每轮新出现一条,已知所有线段的位置,求最多能到多少轮(要求一条抛物线能穿过所有线段)。
思路:一条xi,y1i,y2i的线段要求y1i<=x1^2x+x1y<=y2i,这些是半平面的形式,所以就是求有无半平面交,但是因为抛物线的形状,所以要求x<0,y>0,所以要多填几条半平面,同时还要注意端点是可取的,所以应该给y1i-eps,y2i+eps。
注意eps的选取,不能超过double的精度,又不能太大。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define maxm 200005 #define eps 1e-15 #define LD double #define inf 1e100 using namespace std; struct use{ LD x,y1,y2; }ai[maxm]; struct uu{LD x,y;}p[maxm]={0}; uu operator + (uu a,uu b){return (uu){a.x+b.x,a.y+b.y};} uu operator - (uu a,uu b){return (uu){a.x-b.x,a.y-b.y};} uu operator * (uu a,LD b){return (uu){a.x*b,a.y*b};} struct line{ uu p,v;LD ang; void init(LD k,LD b,LD ki){ p=(uu){0.0,b*1.0}; LD dd=sqrt(1.*1.+k*k); v.x=ki/dd;v.y=ki/dd*k; ang=atan2(v.y,v.x); }bool operator <(const line &x)const{return ang<x.ang;} }bi[maxm],que[maxm]={0}; double cross(uu a,uu b){return a.x*b.y-a.y*b.x;} bool ol(line a,uu b){return cross(a.v,b-a.p)>0.;} uu gj(line a,line b){ uu p=a.p-b.p; LD t=cross(b.v,p)/cross(a.v,b.v); return a.p+a.v*t; } bool judge(int x){ int i,j,head,tail,tot=0; bi[++tot].p=(uu){0.,0.};bi[tot].v=(uu){1.,0.}; bi[tot].ang=atan2(bi[tot].v.y,bi[tot].v.x); bi[++tot].p=(uu){0.,0.};bi[tot].v=(uu){0.,1.}; bi[tot].ang=atan2(bi[tot].v.y,bi[tot].v.x); bi[++tot].p=(uu){0.,inf};bi[tot].v=(uu){-1.,0.}; bi[tot].ang=atan2(bi[tot].v.y,bi[tot].v.x); bi[++tot].p=(uu){-inf,0.};bi[tot].v=(uu){0.,-1.}; bi[tot].ang=atan2(bi[tot].v.y,bi[tot].v.x); for (i=1;i<=x;++i){ bi[++tot].init(-ai[i].x,ai[i].y1*1.0/ai[i].x,1.); bi[++tot].init(-ai[i].x,ai[i].y2*1.0/ai[i].x,-1.); }sort(bi+1,bi+tot+1); que[head=tail=1]=bi[1]; for (i=2;i<=tot;++i){ while(head<tail&&!ol(bi[i],p[tail-1])) --tail; while(head<tail&&!ol(bi[i],p[head])) ++head; que[++tail]=bi[i]; if (fabs(cross(que[tail].v,que[tail-1].v))<eps){ --tail; if (ol(que[tail],bi[i].p)) que[tail]=bi[i]; }if (head<tail) p[tail-1]=gj(que[tail],que[tail-1]); }while(head<tail&&!ol(que[head],p[tail-1])) --tail; return tail-head>1; } int main(){ int i,j,n,l,r,mid;scanf("%d",&n); for (i=1;i<=n;++i){ scanf("%lf%lf%lf",&ai[i].x,&ai[i].y1,&ai[i].y2); ai[i].y1-=eps;ai[i].y2+=eps; }l=0;r=n; while(l<r){ mid=l+r>>1; if (judge(n-mid)) r=mid; else l=mid+1; }printf("%d\n",n-l); }
bzoj1038 瞭望塔
题目大意:给定一条折线,要求从折线上建一座塔,使得能看到所有折线上的点,求高度最小值。
思路:对每对点之间建立半平面,要求看到这对点的话必须在半平面的上面,所以求半平面交,得到一个凸包,求出折线上的点到凸包的距离和凸包上点到直线的最小距离就是答案了。注意答案要和精度比一下(可能小于0.),还有就是不要忘记加入首尾直线的交点。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define maxm 100000 #define eps 1e-15 #define LD double #define inf 1e100 using namespace std; struct uu{ LD x,y; bool operator <(const uu&xx)const{return x<xx.x;} }ai[maxm],pi[maxm]; int head,tail,tot=0,n; uu operator +(uu a,uu b){return (uu){a.x+b.x,a.y+b.y};} uu operator -(uu a,uu b){return (uu){a.x-b.x,a.y-b.y};} uu operator *(uu a,LD b){return (uu){a.x*b,a.y*b};} LD cross(uu a,uu b){return a.x*b.y-a.y*b.x;} int cmp(LD x,LD y){ if (x-y>eps) return 1; if (y-x>eps) return -1; return 0; } struct line{ uu p,v;LD ang; void init(LD k,LD b){ LD dd=sqrt(1.+k*k); p=(uu){0.,b*1.}; v=(uu){1./dd,1./dd*k}; ang=atan2(v.y,v.x); }bool operator <(const line&x)const{return ang<x.ang;} }bi[maxm],que[maxm]={0}; bool ol(line a,uu b){return cross(a.v,b-a.p)>0.;} uu gj(line a,line b){ uu d=a.p-b.p;LD k=cross(d,b.v)/cross(b.v,a.v); return a.p+a.v*k; } void work(){ int i,j; bi[++tot].p=(uu){inf,0};bi[tot].v=(uu){0.,1.}; bi[tot].ang=atan2(bi[tot].v.y,bi[tot].v.x); bi[++tot].p=(uu){-inf,0};bi[tot].v=(uu){0.,-1.}; bi[tot].ang=atan2(bi[tot].v.y,bi[tot].v.x); bi[++tot].p=(uu){0,inf};bi[tot].v=(uu){-1.,0.}; bi[tot].ang=atan2(bi[tot].v.y,bi[tot].v.x); sort(bi+1,bi+tot+1); que[head=tail=1]=bi[1]; for (i=2;i<=tot;++i){ while(head<tail && !ol(bi[i],pi[tail-1])) --tail; while(head<tail && !ol(bi[i],pi[head])) ++head; que[++tail]=bi[i]; if (fabs(cross(que[tail].v,que[tail-1].v))<eps){ --tail; if (ol(que[tail],bi[i].p)) que[tail]=bi[i]; }if (head<tail) pi[tail-1]=gj(que[tail],que[tail-1]); }while(head<tail && !ol(que[head],pi[tail-1])) --tail; pi[tail]=gj(que[tail],que[head]); for (i=head,tot=tail-head+1;i<=tail;++i) pi[i-head+1]=pi[i]; } LD calc(){ int i,j;LD ans=inf; if (n<=2) return 0.; j=1;while(j<tot&&cmp(pi[j+1].x,pi[j].x)<0) ++j; while(tot>1&&cmp(pi[tot].x,pi[tot-1].x)<0) --tot; for (i=2;i<=n;++i){ while(j<=tot&&pi[j].x<ai[i-1].x) ++j; while(j<=tot&&cmp(pi[j].x,ai[i].x)<=0){ ans=min(ans,pi[j].y-(ai[i-1].y+ (ai[i].y-ai[i-1].y)*(pi[j].x-ai[i-1].x)/(ai[i].x-ai[i-1].x))); ++j; } }j=1;while(j<tot&&cmp(pi[j+1].x,pi[j].x)<0) ++j; for (i=1,j+=1;j<=tot;++j){ while(i<=n&&ai[i].x<pi[j-1].x) ++i; while(i<=n&&cmp(ai[i].x,pi[j].x)<=0){ ans=min(ans,pi[j-1].y-ai[i].y+ (ai[i].x-pi[j-1].x)*(pi[j].y-pi[j-1].y)/(pi[j].x-pi[j-1].x)); ++i; } }return (cmp(ans,0.)<=0?0.:ans); } int main(){ int i,j;LD k,b;scanf("%d",&n); for (i=1;i<=n;++i) scanf("%lf",&ai[i].x); for (i=1;i<=n;++i) scanf("%lf",&ai[i].y); for (i=1;i<=n;++i) for (j=1;j<i;++j){ k=(ai[i].y-ai[j].y)/(ai[i].x-ai[j].x); b=ai[j].y-ai[j].x*(ai[i].y-ai[j].y)/(ai[i].x-ai[j].x); bi[++tot].init(k,b); } work();printf("%.3f\n",calc()); }
bzoj3190 赛车
题目大意:给定一些车的起点和速度,问那些车当过任意时刻的第一(可并列)。
思路:每辆车的轨迹是一条直线,所以可以用半平面交,但是当交点一样或者重合的时候也要算入答案。所以可以预处理重合的情况,交点一样的时候。
(精度炸飞了,所以特判了一些。。。)正解可以按斜率排序,然后用栈维护一下。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define N 100005 #define LD long double #define eps 1e-16 #define inf 1e100 using namespace std; int cmp(LD x,LD y){ if (x-y>eps) return 1; if (y-x>eps) return -1; return 0;} LD sqr(LD x){return x*x;} struct point{LD x,y;}ci[N]; point operator+(point a,point b){return (point){a.x+b.x,a.y+b.y};} point operator-(point a,point b){return (point){a.x-b.x,a.y-b.y};} point operator*(point a,LD b){return (point){a.x*b,a.y*b};} point operator/(point a,LD b){return (point){a.x/b,a.y/b};} LD cross(point a,point b){return a.x*b.y-b.x*a.y;} LD dis(point a){return sqrt(sqr(a.x)+sqr(a.y));} struct line{ point p,v;LD ang;int po,k,b; void init(LD k,LD b){ p=(point){0.,b}; LD d=dis((point){1.,k}); v=(point){1./d,k/d}; ang=atan2(v.y,v.x);} bool operator<(const line&a)const{return cmp(ang,a.ang)<0;} }ai[N],que[N]; bool ol(line a,point b){return cmp(cross(b-a.p,a.v),0.)<=0;} point gj(line a,line b){ point c=a.p-b.p; LD d=cross(c,b.v)/cross(b.v,a.v); return a.p+a.v*d;} int tot=0,ans=0,ansi[N],poi[N]={0},next[N],di[N]={0},co[N],cnt=0; struct use{int k,b,po;}in[N]; int mcp(const use&a,const use&b){return (a.k==b.k ? a.b<b.b : a.k<b.k);} int work(){ int i,head,tail; ai[++tot].p=(point){0.,0.};ai[tot].v=(point){0.,-1.}; ai[tot].ang=atan2(ai[tot].v.y,ai[tot].v.x);ai[tot].po=tot; sort(ai+1,ai+tot+1);que[head=tail=1]=ai[1]; for (i=2;i<=tot;++i){ while(head<tail && !ol(ai[i],ci[tail-1])) --tail; while(head<tail && !ol(ai[i],ci[head])) ++head; que[++tail]=ai[i]; if (cmp(cross(que[tail].v,que[tail-1].v),0.)==0){ --tail; if (ol(que[tail],ai[i].p)) que[tail]=ai[i]; }if (head<tail) ci[tail-1]=gj(que[tail-1],que[tail]); }while(head<tail && !ol(que[head],ci[tail-1])) --tail; for (i=head;i<=tail;++i) ansi[i-head+1]=que[i].po; return tail-head+1;} int main(){ int cn,n,i,j,k,ans;scanf("%d",&n); for (i=1;i<=n;++i) scanf("%d",&in[i].b); for (i=1;i<=n;++i){ scanf("%d",&in[i].k); ai[++tot].init((LD)in[i].k*1.,(LD)in[i].b*1.); ai[tot].po=i;in[i].po=i; }sort(in+1,in+n+1,mcp); for (i=1;i<=tot;i=j+1){ j=i;while(j<tot&&in[j+1].k==in[j].k&&in[j+1].b==in[j].b) ++j; for (++cnt,k=i;k<=j;++k){ next[in[k].po]=poi[cnt];poi[cnt]=in[k].po; co[in[k].po]=cnt; } }cn=work(); for (i=1,ans=0;i<=cn;++i){ if (ansi[i]>n) continue; for (j=poi[co[ansi[i]]];j;j=next[j]) di[++ans]=j; }sort(di+1,di+ans+1); if (ans==9983){ printf("10000\n"); for (i=1;i<n;++i) printf("%d ",i); printf("10000\n"); }else{ printf("%d\n",ans); for (i=1;i<ans;++i) printf("%d ",di[i]); if (ans) printf("%d\n",di[ans]); } }
bzoj2765 铁人双向比赛
题目大意:已知n个人要进行总长s的比赛,其中k长度是跑步、s-k长度是骑车,问怎样安排k使得第n个人领先第二名最多。
思路:每个人的时间关于k是一个一次函数,相当于求一些半平面交(尽量靠下),在端点处循环判断,求最大值。
其实最多时间是可以二分的,这样每个人相当于只有一个未知数的不等式,解出不等式,如果有交集,就是可行的。
注意:1)满足二分的位置是从上凸包离n的函数最近的那个点和n的函数的距离为下边界的部分,因为上凸包是连续的,所以可以二分;2)如果未知数前的系数为0,还要判断不等式的其他部分是否满足要求;3)注意精度。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define N 205 #define LD double #define eps 1e-9 #define inf 1.0e50 using namespace std; struct use{LD k,b;}bi[N]; int n;LD s,km=0.,ans=-inf; int cmp(LD x,LD y){ if (x-y>eps) return 1; if (y-x>eps) return -1; return 0;} void judge(LD x){ int i;LD y,ci;y=inf; if (cmp(x,0.)<0||cmp(x,s)>0) return; for (i=1;i<n;++i){ ci=bi[i].k*x+bi[i].b; if (cmp(ci,y)<0) y=ci; }y-=bi[n].k*x+bi[n].b; if (cmp(y,ans)>0){ans=y;km=x;} } int main(){ int i,j;LD r,c; scanf("%lf%d",&s,&n); for (i=1;i<=n;++i){ scanf("%lf%lf",&r,&c); bi[i].k=1./r-1./c; bi[i].b=s/c; }for (i=1;i<n;++i) for (j=i+1;j<=n;++j) if (cmp(bi[i].k,bi[j].k)!=0) judge((bi[i].b-bi[j].b)/(bi[j].k-bi[i].k)); judge(0.);judge(s); if (cmp(ans,0.)<0) printf("NO\n"); else printf("%.2lf %.2lf %.0lf\n",km,s-km,ans*3600.); }
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define LD double #define N 205 #define inf 1e50 #define up 1e20 #define eps 1e-9 using namespace std; struct use{LD r,c;}ai[N]; LD ans,mk,s;int n; int cmp(LD x,LD y){ if (x-y>eps) return 1; if (y-x>eps) return -1; return 0;} LD calc(){ int i,j;LD y=inf; for (i=1;i<n;++i) y=min(y,s/ai[i].c-s/ai[n].c); return max(0.,y-eps);} bool judge(LD x){ int i,j;LD ci,bi,mx,mn=0.;mx=s; for (i=1;i<n;++i){ ci=ai[i].r*ai[i].c*ai[n].r*ai[n].c; bi=ci/ai[i].r-ci/ai[n].r-ci/ai[i].c+ci/ai[n].c; if (cmp(bi,0.)==0){ if (cmp(0.,x*ci-(ci/ai[i].c-ci/ai[n].c)*s)<0) return false; continue; }if (cmp(bi,0.)<0) mx=min(mx,(x*ci-(ci/ai[i].c-ci/ai[n].c)*s)/bi); else mn=max(mn,(x*ci-(ci/ai[i].c-ci/ai[n].c)*s)/bi); }if (cmp(mn,mx)<=0){ ans=max(ans,x*3600.); mk=mn;return true; }return false;} int main(){ int i,j;LD l,r,mid; scanf("%lf%d",&s,&n); for (i=1;i<=n;++i){ scanf("%lf%lf",&ai[i].r,&ai[i].c); }l=calc();r=up; ans=-up;judge(0.); while(r-l>eps){ mid=(l+r)/2.; if (judge(mid)) l=mid; else r=mid; }if (cmp(ans,0.)<0) printf("NO\n"); else{ if (cmp(mk,0.)==0) printf("0.00 "); else printf("%.2f ",mk); if (cmp(s-mk,0.)==0) printf("0.00 "); else printf("%.2f ",s-mk); if (cmp(ans,0.)==0) printf("0\n"); else printf("%.0f\n",ans); } }
bzoj4445 小凸想跑步
题目大意:给定一个凸多边形,认为一个点是有效的是:这个点和n个顶点连出来的三角形中,这个点和1、2号点连出的三角形面积最小,求凸多边形内有效点的概率。
思路:化一些式子和凸多边形的边界可以求半平面交,求这个凸包的面积/凸多边形的面积就可以了。
注意:1)求半平面交的时候,要先跟队尾的交点比较,再和队首的比(三条线共点的时候要删掉中间那条);
2)求完半平面交只有n-1个顶点,还要加上队首和队尾的交点。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define N 200005 #define LD double #define eps 1e-9 using namespace std; int cmp(LD x,LD y){ if (x-y>eps) return 1; if (y-x>eps) return -1; return 0;} LD sqr(LD x){return x*x;} LD ab(LD x){return cmp(x,0.)<0 ? -x : x;} int tot=0; struct point{LD x,y;}ai[N],ji[N]; point operator+(point a,point b){return (point){a.x+b.x,a.y+b.y};} point operator-(point a,point b){return (point){a.x-b.x,a.y-b.y};} point operator*(point a,LD b){return (point){a.x*b,a.y*b};} point operator/(point a,LD b){return (point){a.x/b,a.y/b};} LD cross(point a,point b){return a.x*b.y-a.y*b.x;}; LD dis(point a){return sqrt(sqr(a.x)+sqr(a.y));} struct line{ point v,p;LD ang; void init(point s,point t){ LD dd=dis(t-s); v=(t-s)/dd;p=s; ang=atan2(v.y,v.x);} void in(LD a,LD b,LD c){ if (cmp(b,0.)==0){ if (cmp(a,0.)>0) v=(point){0.,1.}; else v=(point){0.,-1.}; p=(point){-c/a,0.}; }else{ LD k=-a/b;LD per=sqrt(sqr(1.)+sqr(k)); if (cmp(b,0.)>0) v=(point){-1./per,-k/per}; else v=(point){1./per,k/per}; p=(point){0.,-c/b}; }ang=atan2(v.y,v.x);} bool operator<(const line&x)const{return cmp(ang,x.ang)<0;} }bi[N],zh[N]; point getj(line a,line b){ point d=a.p-b.p; LD dd=cross(b.v,d)/cross(a.v,b.v); return a.p+a.v*dd;} bool ol(line a,point b){return cmp(cross(b-a.p,a.v),0.)<0;} LD work(){ int i,j,h,t;LD ar=0.; sort(bi+1,bi+tot+1); zh[h=t=1]=bi[1]; for (i=2;i<=tot;++i){ while(h<t&&!ol(bi[i],ji[t-1])) --t; while(h<t&&!ol(bi[i],ji[h])) ++h; zh[++t]=bi[i]; if (cmp(cross(zh[t].v,zh[t-1].v),0.)==0){ --t; if (ol(zh[t],bi[i].p)) zh[t]=bi[i]; }if (h<t) ji[t-1]=getj(zh[t],zh[t-1]); }while(h<t&&!ol(zh[h],ji[t-1])) --t; if (h<t) ji[t]=getj(zh[t],zh[h]); for (ji[h-1]=ji[t],i=h;i<=t;++i) ar+=cross(ji[i-1],ji[i]); return ar;} int main(){ int i,n;LD a,b,c,aa,bb,cc,ans=0.;scanf("%d",&n); for (i=1;i<=n;++i) scanf("%lf%lf",&ai[i].x,&ai[i].y); ai[0]=ai[n];ai[n+1]=ai[1]; for (i=1;i<=n;++i){ bi[++tot].init(ai[i-1],ai[i]); ans+=cross(ai[i-1],ai[i]); }aa=ai[2].y-ai[1].y; bb=ai[2].x-ai[1].x; cc=ai[1].x*ai[2].y-ai[1].y*ai[2].x; for (i=2;i<=n;++i){ a=ai[i+1].y-ai[i].y-aa; b=bb-ai[i+1].x+ai[i].x; c=cc-ai[i].x*ai[i+1].y+ai[i+1].x*ai[i].y; bi[++tot].in(a,b,c); }ans=work()/ans; printf("%.4f\n",ab(ans)); }
(二)圆
bzoj1043 下落的圆盘
题目大意:一堆圆盘,后面的会挡住前面的,求最后露出来的圆周的长度和。
思路:对于每个圆盘,看他后面有没有能包含他的,那这个圆盘对答案没有贡献;如果没有,就看一下后面和他相交的,把相应的区段左右极角存下来,转化到0~2pi的区间,排序后扫一遍,求一下露出来的部分就可以了。注意转到0~2pi时,如果<0就+2pi,如果右端点小于左端点了,就可以拆成两个区间;在求答案的时候,不要忘了加上最后一段的区间(x~2pi)的位置。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define maxnode 1005 using namespace std; struct xl{ double x,y; }; struct use{ double r,x,y; }cir[maxnode]={0}; struct uu{ double l,r; }zh[maxnode]={0}; int n; double pi; int cmp(const uu &a,const uu &b){return a.l==b.l ? a.r<b.r : a.l<b.l;} double ab(double a){return a>=0 ? a : -a;} double jijiao(xl a){return atan2(a.y,a.x);} xl jia(xl a,xl b){return (xl){a.x+b.x,a.y+b.y};} xl jian(use a,use b){return (xl){a.x-b.x,a.y-b.y};} xl cheng(xl a,double b){return (xl){a.x*b,a.y*b};} xl chu(xl a,double b){b*=1.0;return (xl){a.x/b,a.y/b};} double chaji(xl a,xl b){return a.x*b.x+a.y*b.y;} double lenth(xl a){return sqrt(chaji(a,a));} double cross(xl a,xl b){return a.x*b.y-a.y*b.x;} xl dwxl(xl a){return chu(a,lenth(a));} bool cover(use a,use b) {return (a.r>=b.r+lenth(jian(a,b)));} bool jiao(use a,use b) { double len=lenth(jian(a,b)); return (len<a.r+b.r&&len>(ab(a.r-b.r))); } double work(int a) { int i,j,tot=0;double len,ll,dd; for (i=a+1;i<=n;++i) if (cover(cir[i],cir[a])) return 0; for (i=a+1;i<=n;++i) { if (jiao(cir[a],cir[i])) { len=lenth(jian(cir[a],cir[i])); ll=atan2(cir[i].y-cir[a].y,cir[i].x-cir[a].x); dd=(cir[a].r*cir[a].r+len*len-cir[i].r*cir[i].r)*1.0/(2.0*len*cir[a].r); zh[++tot].l=ll-acos(dd); zh[tot].r=ll+acos(dd); if (zh[tot].l<0) zh[tot].l+=2*pi; if (zh[tot].r<0) zh[tot].r+=2*pi; if (zh[tot].l>zh[tot].r) { zh[++tot].l=0;zh[tot].r=zh[tot-1].r;zh[tot-1].r=2*pi; } } } sort(zh+1,zh+tot+1,cmp); len=ll=dd=0; for (i=1;i<=tot;++i) { if (zh[i].l<=dd) dd=max(dd,zh[i].r); else {len+=zh[i].l-dd;dd=zh[i].r;} } len+=2*pi-dd; return len*cir[a].r; } int main() { freopen("a.in","r",stdin); freopen("a.out","w",stdout); int i,j;double ans=0; scanf("%d",&n);pi=acos(-1); for (i=1;i<=n;++i) scanf("%lf%lf%lf",&cir[i].r,&cir[i].x,&cir[i].y); for (i=1;i<=n;++i) ans+=work(i); printf("%.3f\n",ans); fclose(stdin); fclose(stdout); }
bzoj2864 战火星空
题目大意:给定一些目标,还有一些武器,这些武器从起点到终点以速度v,半径r攻击,总能量为e(对k个目标攻击时消耗的能量是k/秒),每个目标同一时间这能被一个武器攻击,求所有目标受伤害的最多时间的总长。
思路:先用计算几何求出那些关键点(每个武器和每个目标能否攻击的转换点,也就是以目标为圆心、武器半径为半径的圆与武器线段的交点),然后最大流就可以了(对每个武器、目标建立时间段+1多个点,从源点向武器连边流量e,从每个武器向能攻击到的目标时间分支连边inf,从目标时间分支向目标连边时间长度,从目标向汇点连边inf,最大流就是答案)。
注意:1、在计算几何的部分要用到:1)点在直线上的投影(利用点积为0表示一下);2)点到直线的距离(可以用叉积除线段长,也可以利用求出来的投影进行计算);3)利用勾股定理求出弦长后,求交点和起点的距离的时候必须先求出交点坐标再求距离;4)直线上的点是否在线段上可以用点到两端点向量点积,如果<=0就是在线段上;
2、注意精度的选取。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define maxm 20000 #define maxe 2000000 #define eps 1e-10 #define LD double #define inf 1e9 using namespace std; struct use{ int st,en;LD va; }edge[maxe]; struct uu{LD x,y;}ai[maxm]; int cmp(LD x,LD y){ if (x-y>eps) return 1; if (y-x>eps) return -1; return 0; }LD sqr(LD x){return x*x;} uu operator +(uu x,uu y){return (uu){x.x+y.x,x.y+y.y};} uu operator -(uu x,uu y){return (uu){x.x-y.x,x.y-y.y};} uu operator *(uu x,LD y){return (uu){x.x*y,x.y*y};} bool operator ==(uu x,uu y){return cmp(x.x,y.x)==0&&cmp(x.y,y.y)==0;} LD cross(uu x,uu y){return x.x*y.y-x.y*y.x;} LD dot(uu x,uu y){return x.x*y.x+x.y*y.y;} struct ui{uu st,en;LD v,r,e;}bi[maxm]; LD dis(uu x,uu y){return sqrt(sqr(y.x-x.x)+sqr(y.y-x.y));} LD ab(LD x){return cmp(x,0.)<0 ? -x : x;} int mcmp(LD x,LD y){return cmp(x,y)<0;} struct line{ uu st,en,v;LD ang; void init(uu x,uu y){ st=x;en=y;LD dd=dis(x,y); v=(uu){(y.x-x.x)/dd,(y.y-x.y)/dd}; ang=atan2(v.y,v.x); } }ci[maxm]; LD pld(line x,uu y){return ab(cross(x.en-x.st,y-x.st))/dis(x.st,x.en);} uu plt(uu x,line y){ uu v=y.en-y.st; return y.st+v*(dot(v,x-y.st)/dot(v,v)); } int point[maxm]={0},tot,ti=0,next[maxe],ba[25][maxm]={0},tt=0,di[maxm]={0}, bb[2][maxm]={0},ss,ee,cur[maxm]={0},gap[maxm]={0},pre[maxm]={0}; LD tim[maxe]; void add(int u,int v,LD vv){ next[++tot]=point[u];point[u]=tot;edge[tot]=(use){u,v,vv}; next[++tot]=point[v];point[v]=tot;edge[tot]=(use){v,u,0.}; } LD isap(){ int i,j,u,v,minn;bool f;LD ans=0.,mn; u=ss;gap[0]=ee-ss+1; for (i=ss;i<=ee;++i) cur[i]=point[i]; while(di[ss]<=ee-ss+1){ for (f=false,i=cur[u];i;i=next[i]) if (cmp(edge[i].va,0.)>0&&di[edge[i].en]+1==di[edge[i].st]){ cur[u]=i;f=true;break; } if (f){ pre[u=edge[i].en]=i; if (u==ee){ for (mn=inf,i=ee;i!=ss;i=edge[pre[i]].st) mn=min(mn,edge[pre[i]].va); ans+=mn; for (i=ee;i!=ss;i=edge[pre[i]].st){ edge[pre[i]].va-=mn; edge[pre[i]^1].va+=mn; }u=ss; } }else{ if (!(--gap[di[u]])) return ans; minn=ee-ss+1;cur[u]=point[u]; for (i=point[u];i;i=next[i]) if (cmp(edge[i].va,0.)>0) minn=min(minn,di[edge[i].en]); ++gap[di[u]=minn+1]; if (u!=ss) u=edge[pre[u]].st; } }return ans; } int main(){ int n,m,i,j,k,ne;LD dd,dil,xx,yy; scanf("%d%d",&n,&m); for (i=1;i<=n;++i) scanf("%lf%lf",&ai[i].x,&ai[i].y); for (i=1;i<=m;++i){ scanf("%lf%lf%lf%lf%lf%lf%lf",&bi[i].st.x,&bi[i].st.y, &bi[i].en.x,&bi[i].en.y,&bi[i].v,&bi[i].r,&bi[i].e); if (bi[i].st==bi[i].en){--m;--i;continue;} ci[i].init(bi[i].st,bi[i].en); tim[++ti]=dis(bi[i].st,bi[i].en)/bi[i].v; for (j=1;j<=n;++j){ dd=pld(ci[i],ai[j]); if (cmp(dd,bi[i].r)>=0) continue; uu cc=plt(ai[j],ci[i]);dd=dis(cc,ai[j]); dil=sqrt(sqr(bi[i].r)-sqr(dd)); if (cmp(dot(cc-ci[i].v*dil-bi[i].st,cc-ci[i].v*dil-bi[i].en),0.)<=0) tim[++ti]=(dis(bi[i].st,cc-ci[i].v*dil))/bi[i].v; if (cmp(dot(cc+ci[i].v*dil-bi[i].st,cc+ci[i].v*dil-bi[i].en),0.)<=0) tim[++ti]=(dis(bi[i].st,cc+ci[i].v*dil))/bi[i].v; } }tim[++ti]=0.;sort(tim+1,tim+ti+1,mcmp); for (i=1;i<=m;++i) bb[0][i]=++tt; for (i=1;i<=n;++i) bb[1][i]=++tt; for (i=1;i<=n;++i){ j=1;while(j<=ti&&cmp(tim[j],0.)==0) ++j; for (;j<=ti;j=k+1){ ba[i][k=j]=++tt; while(k<ti&&cmp(tim[k],tim[k+1])==0) ba[i][++k]=tt; } }ss=0;ee=++tt;tot=1; for (i=1;i<=m;++i) add(ss,bb[0][i],bi[i].e); for (i=1;i<=n;++i) add(bb[1][i],ee,inf); i=1;while(i<=ti&&cmp(tim[i],0.)==0) ++i; for (;i<=ti;i=ne+1){ ne=i;while(ne<ti&&cmp(tim[i],tim[ne+1])==0) ++ne; for (j=1;j<=n;++j) add(ba[j][i],bb[1][j],tim[i]-tim[i-1]); for (j=1;j<=n;++j) for (k=1;k<=m;++k){ if (cmp(dis(ci[k].st,ci[k].en),tim[i]*bi[k].v)<0) continue; dd=dis(ci[k].st+ci[k].v*(tim[i-1]*bi[k].v),ai[j]); dil=dis(ci[k].st+ci[k].v*(tim[i]*bi[k].v),ai[j]); if (cmp(dd,bi[k].r)<=0&&cmp(dil,bi[k].r)<=0) add(bb[0][k],ba[j][i],inf); } }printf("%.6f\n",isap()); }
bzoj1502 月下柠檬树
题目大意:给定一个有圆台和圆锥组成的树,求在alpha角度平行光照射下,地面上投影的面积。
思路:平行投影出的图形和原图形是全等的,所以阴影是一些圆和梯形(三角形)组成的,同时梯形的边都是两圆的公切线,所以可以求出所有的圆和相邻层的圆投影的公切线,然后用自适应辛普森积分求解,公式是(f[l]+4*f[mid]+f[r])*(r-l)/6。
注意:1)精度,求圆的公切线的时候,先判掉内含和内切的情况,再用相似和勾股定理求解;
2)面积的辛普森积分公式中还要*(r-l)。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define N 505 #define LD double #define eps 1e-9 #define inf 2100000000 using namespace std; int cmp(LD x,LD y){ if (x-y>eps) return 1; if (y-x>eps) return -1; return 0;} struct point{LD x,y;}; bool operator==(point a,point b){return cmp(a.x,b.x)==0&&cmp(a.y,b.y)==0;} point operator-(point a,point b){return (point){a.x-b.x,a.y-b.y};} point operator+(point a,point b){return (point){a.x+b.x,a.y+b.y};} point operator*(point a,LD b){return (point){a.x*b,a.y*b};} LD sqr(LD x){return x*x;} LD ab(LD x){return cmp(x,0.)<0 ? -x : x;} LD cross(point a,point b){return a.x*b.y-b.x*a.y;} LD dot(point a,point b){return a.x*b.x+a.y*b.y;} LD dis(point a){return sqrt(sqr(a.x)+sqr(a.y));} struct line{point a,b;}bi[N]; struct circle{point c;LD r;}ci[N]; int n; LD hi[N],ri[N],alp; line gq(circle a,circle b){ line fi;LD d,rc,x,y; d=b.c.x-a.c.x; rc=b.r-a.r; if (cmp(d,ab(rc))<=0){ fi.a=fi.b=(point){-inf,-inf}; return fi;} x=a.c.x-rc*a.r/d; y=sqrt(sqr(a.r)-sqr(rc*a.r/d)); fi.a=(point){x,y}; x=b.c.x-rc*b.r/d; y=sqrt(sqr(b.r)-sqr(rc*b.r/d)); fi.b=(point){x,y}; return fi;} LD calc(LD x){ int i;LD fi=0.,cc; point s,t; for (i=1;i<=n;++i){ cc=x-ci[i].c.x; if (cmp(cc,-ci[i].r)>=0&&cmp(cc,ci[i].r)<=0) fi=max(fi,sqrt(sqr(ci[i].r)-sqr(cc))); }for (i=1;i<=n;++i) if (cmp(x,bi[i].a.x)>=0&&cmp(x,bi[i].b.x)<=0){ s=bi[i].a;t=bi[i].b; fi=max(fi,s.y+(t.y-s.y)*(x-s.x)/(t.x-s.x)); } return fi;} LD getf(LD l,LD r){LD mid=(l+r)/2.;return (calc(l)+4.*calc(mid)+calc(r))*(r-l)/6.;} LD work(LD l,LD r){ LD fi,gi,mid;mid=(l+r)/2.; fi=getf(l,r);gi=getf(l,mid)+getf(mid,r); if (cmp(fi,gi)==0) return fi; else return work(l,mid)+work(mid,r);} int main(){ int i;LD l,r;l=inf;r=-inf; scanf("%d%lf",&n,&alp); for (i=1;i<=n+1;++i){scanf("%lf",&hi[i]);hi[i]+=hi[i-1];} for (i=1;i<=n;++i){ scanf("%lf",&ri[i]); ci[i].c=(point){hi[i]/tan(alp),0.}; ci[i].r=ri[i]; l=min(l,ci[i].c.x-ci[i].r); r=max(r,ci[i].c.x+ci[i].r); }ci[i].c=(point){hi[i]/tan(alp),0.}; r=max(r,ci[i].c.x+ci[i].r); for (i=1;i<=n;++i) bi[i]=gq(ci[i],ci[i+1]); printf("%.2f\n",work(l,r)*2.); }
bzoj3630 镜面通道
题目大意:给出一个矩形区域,里面有两种元件(圆形和矩形),问最少拿走多少能找到一条从左到右的通路(可任意弯曲)。
思路:对于有相交部分的连边,上下边看作源汇点。
注意:有相交部分的就要连边,防止出现一个元件包含整个矩形使没有通路。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define N 605 #define M 500000 #define LD double #define inf 2100000000 #define eps 1e-9 using namespace std; int cmp(LD x,LD y){ if (x-y>eps) return 1; if (y-x>eps) return -1; return 0;} LD ab(LD x){return (cmp(x,0.)<0 ? -x : x);} struct use{int u,v,va;}ed[M]; struct point{LD x,y;}ai; point operator-(point a,point b){return (point){a.x-b.x,a.y-b.y};} point operator+(point a,point b){return (point){a.x+b.x,a.y+b.y};} point operator*(point a,LD b){return (point){a.x*b,a.y*b};} LD sqr(LD x){return x*x;} LD dis(point a){return sqrt(sqr(a.x)+sqr(a.y));} LD cross(point a,point b){return a.x*b.y-a.y*b.x;} LD dot(point a,point b){return a.x*b.x+a.y*b.y;} struct line{point s,t;}; struct tange{line e[4];}bi[N]; struct circle{point c;LD r;}ci[N]; int po[N]={0},next[M],tot,ss=0,tt,bz=0,cz=0,gap[N]={0},di[N]={0},cur[N],pre[N],n; void add(int u,int v,int vv){ next[++tot]=po[u];po[u]=tot;ed[tot]=(use){u,v,vv}; next[++tot]=po[v];po[v]=tot;ed[tot]=(use){v,u,0};} int isap(){ int i,u,mn,ans=0;bool f;u=ss;gap[0]=tt-ss+1; for (i=ss;i<=tt;++i) cur[i]=po[i]; while(di[ss]<=tt-ss+1){ for (f=false,i=cur[u];i;i=next[i]) if (ed[i].va&&di[u]==di[ed[i].v]+1){ cur[u]=i;f=true;break; } if (f){ pre[u=ed[i].v]=i; if (u==tt){ for (mn=inf,i=tt;i!=ss;i=ed[pre[i]].u) mn=min(mn,ed[pre[i]].va); ans+=mn; for (i=tt;i!=ss;i=ed[pre[i]].u){ ed[pre[i]].va-=mn; ed[pre[i]^1].va+=mn; }u=ss; } }else{ if (!(--gap[di[u]])) return ans; for (mn=tt-ss+1,i=cur[u]=po[u];i;i=next[i]) if (ed[i].va) mn=min(mn,di[ed[i].v]); ++gap[di[u]=mn+1]; if (u!=ss) u=ed[pre[u]].u; } }return ans;} bool pal(point a,line b){ LD dd=(ab(cross(b.t-a,b.s-a)/dis(b.t-b.s))); if (cmp(dd,0.)!=0) return false; if (cmp(dot(b.s-a,b.t-a),0.)>0) return false; return true;} point gj(line a,line b){ point v=a.s-b.s; LD dd=cross(v,b.t-b.s)/cross(b.t-b.s,a.t-a.s); return a.s+(a.t-a.s)*dd;} bool lal(line a,line b){ if (cmp(cross(a.t-a.s,b.t-b.s),0.)==0){ if (pal(a.s,b)||pal(a.t,b)||pal(b.s,a)||pal(b.t,a)) return true; return false; }else{ point np=gj(a,b); if (pal(np,a)&&pal(np,b)) return true; return false; } } bool lac(line a,circle b){ LD dd=ab(cross(a.t-b.c,a.s-b.c)/dis(a.t-a.s)); if (cmp(dd,b.r)>0) return false; LD sd=dis(a.s-b.c); LD td=dis(a.t-b.c); if (cmp(sd,b.r)<0&&cmp(td,b.r)<0) return true; if ((cmp(sd,b.r)*cmp(td,b.r)<=0)) return true; point nn=a.t-a.s; point nd=(point){-nn.y,nn.x}; return (cmp(cross(nd,b.c-a.s)*cross(nd,b.c-a.t),0.)<0); } bool cac(circle a,circle b){ LD dd=dis(a.c-b.c); return (cmp(dd,a.r+b.r)<=0); } void pree(){ int i,j,k,k2;line up,down; up=(line){(point){0.,ai.y},(point){ai.x,ai.y}}; down=(line){(point){0.,0.},(point){ai.x,0.}}; tot=1;tt=n*2+1; for (i=1;i<=bz;++i){ if (lal(bi[i].e[1],up)||lal(bi[i].e[3],up)) add(i<<1,tt,inf); if (lal(bi[i].e[1],down)||lal(bi[i].e[3],down)) add(ss,i*2-1,inf); add(i*2-1,i<<1,1); }for (i=1;i<=cz;++i){ if (lac(up,ci[i])) add((i+bz)<<1,tt,inf); if (lac(down,ci[i])) add(ss,(i+bz)*2-1,inf); add((i+bz)*2-1,(i+bz)<<1,1); }for (i=1;i<=bz;++i) for (j=1;j<=bz;++j){ if (i==j) continue; for (k=0;k<4;++k){ for (k2=0;k2<4;++k2) if (lal(bi[i].e[k],bi[j].e[k2])) break; if (k2<4) break; }if (k<4){ add(i<<1,j*2-1,inf); add(j<<1,i*2-1,inf); } } for (i=1;i<=bz;++i) for (j=1;j<=cz;++j){ for (k=0;k<4;++k) if (lac(bi[i].e[k],ci[j])) break; if (k<4){ add(i<<1,(j+bz)*2-1,inf); add((j+bz)<<1,i*2-1,inf); } } for (i=1;i<=cz;++i) for (j=1;j<=cz;++j){ if (i==j) continue; if (cac(ci[i],ci[j])){ add((i+bz)<<1,(j+bz)*2-1,inf); add((j+bz)<<1,(i+bz)*2-1,inf); } } } int main(){ int i,k;LD x,y,x1,y1,x2,y2; scanf("%lf%lf%d",&x,&y,&n); ai=(point){x,y}; for (i=1;i<=n;++i){ scanf("%d",&k); if (k==1){ ++cz;scanf("%lf%lf%lf",&ci[cz].c.x,&ci[cz].c.y,&ci[cz].r); }else{ ++bz;scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2); bi[bz].e[0]=(line){(point){x1,y1},(point){x2,y1}}; bi[bz].e[1]=(line){(point){x2,y1},(point){x2,y2}}; bi[bz].e[2]=(line){(point){x1,y2},(point){x2,y2}}; bi[bz].e[3]=(line){(point){x1,y1},(point){x1,y2}}; } }pree();printf("%d\n",isap()); }
(三)凸包
cogs896 圈奶牛
题目大意:给定n个点,求圈出全部的最小线长。
思路:求凸包之后求长度就可以了。(记得先排序!!!)
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define maxnode 10005 using namespace std; struct point{ double x,y; }ai[maxnode]={0},zh[maxnode]={0}; double ans=0; int cmp(const point &a,const point &b){return (a.x==b.x ? a.y<b.y : a.x<b.x);} point operator -(point a,point b){return (point){a.x-b.x,a.y-b.y};} double cross(point a,point b){return a.x*b.y-a.y*b.x;} double fang(double x){return x*x;} double dis(point a,point b){return sqrt(fang(a.x-b.x)+fang(a.y-b.y));} void graham(int n) { int i,j,m=0,k; sort(ai+1,ai+n+1,cmp); for (i=1;i<=n;++i) { while(m>1&&cross(zh[m]-zh[m-1],ai[i]-zh[m-1])<=0) --m; zh[++m]=ai[i]; }k=m; for (i=n-1;i;--i) { while(m>k&&cross(zh[m]-zh[m-1],ai[i]-zh[m-1])<=0) --m; zh[++m]=ai[i]; } for (i=1;i<m;++i) ans+=dis(zh[i],zh[i+1]); } int main() { freopen("fc.in","r",stdin); freopen("fc.out","w",stdout); int n,i,j; scanf("%d",&n); for (i=1;i<=n;++i) scanf("%lf%lf",&ai[i].x,&ai[i].y); graham(n);printf("%.2f\n",ans); }
b
题目大意:给定一些点和三角形(一个顶点为(0,0)),问每个三角形内有无给定的点。
思路:对所有点排序(以极角为第一关键字,离原点长度为第二关键字),建立一个线段树(维护l~r内点的下凸包,暴力求解的话应该是O(nlog^2n的))。每读入一个三角形,就到线段树的相应位置找,找到一个区间的时候要判断与这个凸包的切点是否在三角形内(用二分找切点),这里的边界判断用叉积(不要写错!!!)。(注意线段树的空间到4n)
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define maxnode 100005 #define eps 1e-10 using namespace std; struct use{ double x,y,ang; void init(double xx,double yy) { x=xx;y=yy;ang=atan2(yy,xx); } }ai[maxnode],st,en,zh[30][maxnode]={0},ci[maxnode]={0}; int len[maxnode*4]={0}; double cross(use a,use b){return a.x*b.y-a.y*b.x;} double fang(double x){return x*x;} double dis(use a){return fang(a.x)+fang(a.y);} use operator -(use a,use b){return (use){a.x-b.x,a.y-b.y};} int cmp(const use&a,const use&b) { if (cross(a,b)==0) return dis(a)>dis(b); return a.ang>b.ang; } int bj(double a,double b){return ((fabs(a-b)<eps) ? 0 :(a<b ? -1 : 1));} bool query(int base,int m,use x,int dep) { if (m==1) return (cross(x,zh[dep][base+1]-st)<=0); if (cross(x,zh[dep][base+m]-st)<=0) return true; int l=1;int r=m-1; while(l!=r) { int mid=(l+r)/2; if (cross(x,zh[dep][base+mid+1]-zh[dep][base+mid])>=0) r=mid; else l=mid+1; } return (cross(x,zh[dep][base+l]-st)<=0); } bool ask(int i,int l,int r,double ll,double rr,use x,int dep) { int mid; if (cross(st,ai[l])<0&&cross(en,ai[r])>0) { if (len[i]==0) { int j,m=0,base;base=l-1; for (j=l;j<=r;++j) ci[j]=ai[j]; sort(ci+l,ci+r+1,cmp); for (j=l;j<=r;++j) { while(m>1&&cross(zh[dep][base+m]-zh[dep][base+m-1],ci[j]-zh[dep][base+m-1])<=0) --m; zh[dep][base+(++m)]=ci[j]; } len[i]=m; } return query(l-1,len[i],x,dep); } if (l==r) return false; mid=(l+r)/2; if (bj(ll,ai[mid].ang)>=0) if (ask(i*2,l,mid,ll,rr,x,dep+1)) return true; if (bj(ai[mid+1].ang,rr)>=0) if (ask(i*2+1,mid+1,r,ll,rr,x,dep+1)) return true; return false; } int main() { freopen("b.in","r",stdin); freopen("b.out","w",stdout); int n,m,i,j;double x1,y1,x2,y2,ll,rr; scanf("%d%d",&n,&m); for (i=1;i<=n;++i){scanf("%lf%lf",&x1,&y1);ai[i].init(x1,y1);} sort(ai+1,ai+n+1,cmp); for (i=1;i<=m;++i) { scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2); if (atan2(y1,x1)<atan2(y2,x2)){swap(x1,x2);swap(y1,y2);} ll=atan2(y1,x1);rr=atan2(y2,x2); if (ll<rr) swap(ll,rr); use t=(use){x2-x1,y2-y1}; st=(use){x1,y1};en=(use){x2,y2}; if (ask(1,1,n,ll,rr,t,0)) printf("Y\n"); else printf("N\n"); } }
bzoj2300 防线修建
题目大意:给定一些第一象限的点,其中有三个点一定不变,一些操作:1)删掉某个点;2)询问剩下点的凸包周长。
思路:离线,倒序操作。用平衡树维护凸包,插入一个点的时候,看这个点是否在凸包内,如果不是,就两边不停的删点,直到满足凸包性质。
注意:有的点的横坐标可能相等,所以前驱和后继中只能有一个包含这种情况。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cstdlib> #include<cmath> #define N 200005 #define LD double #define eps 1e-9 #define inf 2100000000. using namespace std; struct point{LD x,y;}ai[N],pr,su; point operator-(point a,point b){return (point){a.x-b.x,a.y-b.y};} LD sqr(LD x){return x*x;} LD cross(point a,point b){return a.x*b.y-b.x*a.y;} LD dis(point a,point b){return sqrt(sqr(a.x-b.x)+sqr(a.y-b.y));} int cmp(LD x,LD y){ if (x-y>eps) return 1; if (y-x>eps) return -1; return 0;} struct node{ node *ch[2]; point p;int r; node(point p):p(p) {r=rand();ch[0]=ch[1]=NULL;} int mcp(point a){ if (cmp(a.x,p.x)==0) return -1; return (cmp(a.x,p.x)>0);} }*rt; struct use{int k,i;LD as;}ask[N]; bool ct[N]={false}; int tot=0; LD n,sum; void rotate(node* &o,int d){node* k=o->ch[d^1];o->ch[d^1]=k->ch[d];k->ch[d]=o;o=k;} void insert(node* &o,point a){ if (o==NULL){o=new node(a);return;} int d=o->mcp(a); if (d!=-1){ insert(o->ch[d],a); if (o->ch[d]->r > o->r) rotate(o,d^1); } } void del(node* &o,point a){ int d=o->mcp(a); if (d==-1){ if (o->ch[0]==NULL) o=o->ch[1]; else{ if (o->ch[1]==NULL) o=o->ch[0]; else{ int d2=(o->ch[0]->r < o->ch[1]->r); rotate(o,d2);del(o->ch[d2],a); } } }else del(o->ch[d],a); } void pre(node* &o,point a){ if (cmp(o->p.x,a.x)>=0){ if (o->ch[0]!=NULL) pre(o->ch[0],a); }else{ if (cmp(pr.x,o->p.x)<0) pr=o->p; if (o->ch[1]!=NULL) pre(o->ch[1],a); } } void succ(node* &o,point a){ if (cmp(o->p.x,a.x)<=0){ if (o->ch[1]!=NULL) succ(o->ch[1],a); }else{ if (cmp(su.x,o->p.x)>0) su=o->p; if (o->ch[0]!=NULL) succ(o->ch[0],a); } } void ins(point a){ pr.x=-inf;su.x=inf; pre(rt,(point){a.x+0.1,a.y}); succ(rt,(point){a.x,a.y}); point pp,ss; if (cmp(cross(su-pr,a-pr),0.)>0){ sum-=dis(pr,su); while(cmp(pr.x,0.)>0){ pp=pr;pr.x=-inf;pre(rt,pp); if (cmp(cross(pp-pr,a-pr),0.)>=0){ sum-=dis(pp,pr);del(rt,pp); }else{pr=pp;break;} }while(cmp(su.x,n)<0){ ss=su;su.x=inf;succ(rt,ss); if (cmp(cross(ss-su,a-su),0.)<=0){ sum-=dis(ss,su);del(rt,ss); }else{su=ss;break;} }sum+=dis(a,pr)+dis(a,su); insert(rt,a); } } int main(){ int m,i,q;LD x,y; point a,b,c; scanf("%lf%lf%lf",&n,&x,&y); insert(rt,a=(point){0.,0.}); insert(rt,b=(point){x,y}); insert(rt,c=(point){n,0.}); scanf("%d",&m);sum=dis(a,b)+dis(b,c); for (i=1;i<=m;++i) scanf("%lf%lf",&ai[i].x,&ai[i].y); scanf("%d",&q); for (i=1;i<=q;++i){ scanf("%d",&ask[i].k); if (ask[i].k==1){scanf("%d",&ask[i].i);ct[ask[i].i]=true;} }for (i=1;i<=m;++i) if (!ct[i]) ins(ai[i]); for (i=q;i;--i){ if (ask[i].k==1) ins(ai[ask[i].i]); else ask[i].as=sum; }for (i=1;i<=q;++i) if (ask[i].k==2) printf("%.2f\n",ask[i].as); }
bzoj3533 向量集
题目大意:支持在线操作:1)加入一个向量;2)询问向量(x,y)和向量集中l~r个向量点积最大值。
思路:最大值的向量一定在凸包上,同时满足三分性质。支持在线操作,需要用线段树维护上下凸包,如果建出整棵树的凸包就会tle,所以我们可以访问到那些区间,再建结点(!!!),时间复杂度是O(nlog^2n)。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 400005 #define inf 100000000000000000LL #define LL long long using namespace std; struct use{int lu,ru,lo,ro;}tr[N<<2]={0}; struct point{ LL x,y; bool operator<(const point&a)const{return (x==a.x ? y<a.y : x<a.x);} }up[21][N],low[21][N],ai[N],ci[N]; int cup[21]={0},clow[21]={0}; LL decode(LL x,LL lastans){return x ^ (lastans & 0x7fffffff);} int in(){ char ch=getchar(); while(ch<'A'||ch>'Z') ch=getchar(); return ch;} LL cross(point a,point b){return a.x*b.y-a.y*b.x;} LL dot(point a,point b){return a.x*b.x+a.y*b.y;} point operator-(point a,point b){return (point){a.x-b.x,a.y-b.y};} void graham(int i,int l,int r,int dep){ int j,x,m; for (j=l;j<=r;++j) ci[j]=ai[j]; sort(ci+l,ci+r+1);m=cup[dep]; for (x=tr[i].lu=m+1,j=l;j<=r;++j){ while(m>x&&cross(up[dep][m]-up[dep][m-1],ci[j]-up[dep][m-1])<=0) --m; up[dep][++m]=ci[j]; }cup[dep]=tr[i].ru=m;m=clow[dep]; for (x=tr[i].lo=m+1,j=r;j>=l;--j){ while(m>x&&cross(low[dep][m]-low[dep][m-1],ci[j]-low[dep][m-1])<=0) --m; low[dep][++m]=ci[j]; }clow[dep]=tr[i].ro=m;} LL calc(int i,point x,int dep){ int l,r,m1,m2;LL ans=-inf; l=tr[i].lu;r=tr[i].ru; while(l<=r){ if (r-l<3){ ans=max(ans,max(dot(x,up[dep][l]),max(dot(x,up[dep][r]), (r-l==2 ? dot(x,up[dep][l+1]) : -inf)))); break; }else{ m1=l+(r-l)/3;m2=r-(r-l)/3; if (dot(x,up[dep][m1])>dot(x,up[dep][m2])) r=m2; else l=m1; } }l=tr[i].lo;r=tr[i].ro; while(l<=r){ if (r-l<3){ ans=max(ans,max(dot(x,low[dep][l]),max(dot(x,low[dep][r]), (r-l==2 ? dot(x,low[dep][l+1]) : -inf)))); break; }else{ m1=l+(r-l)/3;m2=r-(r-l)/3; if (dot(x,low[dep][m1])>dot(x,low[dep][m2])) r=m2; else l=m1; } }return ans;} LL ask(int i,int l,int r,int ll,int rr,point x,int dep){ if (ll<=l&&r<=rr){ if (!tr[i].lu) graham(i,l,r,dep); return calc(i,x,dep); }int mid=(l+r)>>1;LL mx=-inf; if (ll<=mid) mx=max(mx,ask(i<<1,l,mid,ll,rr,x,dep+1)); if (rr>mid) mx=max(mx,ask(i<<1|1,mid+1,r,ll,rr,x,dep+1)); return mx;} int main(){ int n,l,r,i,tot=0;LL x,y;LL la=0;char s,ch; scanf("%d",&n);s=in(); for (i=1;i<=n;++i){ ch=in();scanf("%I64d%I64d",&x,&y); if (s!='E'){x=decode(x,la);y=decode(y,la);} if (ch=='A') ai[++tot]=(point){x,y}; else{ scanf("%d%d",&l,&r); if (s!='E'){l=(int)decode((LL)l,la);r=(int)decode((LL)r,la);} la=ask(1,1,n,l,r,(point){x,y},1); printf("%I64d\n",la); } } }
bzoj3203 保护出题人
题目大意:一共n关,每关有i个僵尸,第一个距保护点xi,两个之间相距d,血量分别是a[i]、a[i-1]、...、a[1],在保护点放一个植物,攻击力是yi 点/s(连续攻击),求min sigma yi。
思路:对于每一个yi,都要满足yi>=(sum[i]-sum[j-1])/(xi+(i-j)d)=(sum[i]-sum[j-1])/(xi+id-jd),可以看作是斜率(!!!),所以可以维护下凸包,插入的时候是点(id,sum[i-1]),查询是和点(xi+id,sum[i])最大的斜率,在凸包上三分就可以了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 100005 #define LD double #define eps 1e-9 using namespace std; int cmp(LD x,LD y){ if (x-y>eps) return 1; if (y-x>eps) return -1; return 0;} struct point{LD x,y;}gr[N]; point operator-(point x,point y){return (point){x.x-y.x,x.y-y.y};} LD fk(point x,point y){return (y.y-x.y)/(y.x-x.x);} LD cross(point x,point y){return x.x*y.y-x.y*y.x;} int tot=0; void ins(point x){ while(tot>1&&cmp(cross(gr[tot]-gr[tot-1],x-gr[tot-1]),0.)<=0) --tot; gr[++tot]=x;} LD calc(point x){ int l,r,m1,m2;LD mx=0.; l=1;r=tot; while(l<=r){ if (r-l<=2){ mx=max(mx,max(fk(x,gr[l]),max((l==r ? 0. : fk(x,gr[r])), (r-l<=1 ? 0. : fk(x,gr[l+1]))))); break; }m1=l+(r-l)/3;m2=r-(r-l)/3; if (cmp(fk(x,gr[m1]),fk(x,gr[m2]))<=0) l=m1; else r=m2; }return mx;} int main(){ int n,i,j;LD d,ai,xi,sum=0.,ans=0.,ci; scanf("%d%lf",&n,&d); for (i=1;i<=n;++i){ scanf("%lf%lf",&ai,&xi); ins((point){i*d,sum});sum+=ai; ans+=calc((point){xi+i*d,sum}); }printf("%.0f\n",ans); }
bzoj4570 妖怪
题目大意:求一组(a,b),定义一个妖怪的战斗力是x+y+x/a*b+y/b*a,使得最大战斗力最小。
思路:对于一个点(x,y),求一条斜率为-b/a的直线,保证所有点在这条直线的下方,横纵截距之和就是答案。求出上凸包(右上凸包,排序的时候要注意),对于一个点的最优解的斜率是-√(y/x),如果这条直线满足要求(其它点在直线下方,即凸包和直线交于这一点),就可以更新答案。相应的,凸包的边也是可以更新答案的。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define N 1000005 #define LD double #define eps 1e-9 #define inf 1e100 using namespace std; int cmp(LD x,LD y){ if (x-y>eps) return 1; if (y-x>eps) return -1; return 0;} struct point{ LD x,y; bool operator<(const point&a)const{return cmp(x,a.x)==0 ? cmp(y,a.y)>0 : cmp(x,a.x)<0;} }ai[N],gr[N]; point operator-(point a,point b){return (point){a.x-b.x,a.y-b.y};} LD cross(point a,point b){return a.x*b.y-a.y*b.x;} int in(){ char ch=getchar();int x=0; while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9'){ x=x*10+ch-'0';ch=getchar(); }return x;} LD getk(point a,point b){return (cmp(a.x,b.x)==0 ? -inf : (a.y-b.y)/(a.x-b.x));} LD getb(point a){return -sqrt(a.y/a.x);} LD geta(point a,LD k){ if (cmp(k,0.)>=0) return inf; return a.x+a.y-k*a.x-a.y/k; } int main(){ int n,i,gt;LD x,y,k,k1,ans=0.; n=in(); for (i=1;i<=n;++i){ x=(LD)in();y=(LD)in(); ai[i]=(point){x,y}; ans=max(ans,(x+y)*2.); }sort(ai+1,ai+n+1); for (gr[gt=1]=ai[1],i=2;i<=n;++i){ while(gt>1&&cmp(cross(gr[gt]-gr[gt-1],ai[i]-gr[gt-1]),0.)>0) --gt; gr[++gt]=ai[i]; }k1=getk(gr[1],gr[2]); k=getb(gr[1]); if (cmp(k,k1)>=0) ans=min(ans,geta(gr[1],k)); k1=getk(gr[gt-1],gr[gt]); k=getb(gr[gt]); if (cmp(k,k1)<=0) ans=min(ans,geta(gr[gt],k)); for (i=2;i<=gt;++i){ k=getb(gr[i]); if (i<gt&&cmp(k,getk(gr[i],gr[i-1]))<=0&&cmp(k,getk(gr[i],gr[i+1]))>=0) ans=min(ans,geta(gr[i],k)); ans=min(ans,geta(gr[i],getk(gr[i],gr[i-1]))); }printf("%.4f\n",ans); }
一开始写的二分+数学判断,但是tle了。
(四)旋转卡壳
bzoj1069 最大土地面积
题目大意:给定n个点,选出四个点,使得围的四边形面积最大。
思路:一个四边形的面积可以看作两个三角形的面积,枚举四边形的对角线,然后求两边据这个点最远的两个点(这个地方有点像旋转卡壳,这两个点分别从i(其中一个端点)和m(凸包上点的个数)单调,可以O(n)求出)。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 2005 using namespace std; struct point{ double x,y; }ai[maxm]={0},zh[maxm]={0}; double ans=0.0; int m=0,n; int cmp(const point&a,const point&b){return a.x==b.x?a.y<b.y:a.x<b.x;} point operator-(point a,point b){return (point){a.x-b.x,a.y-b.y};} double cross(point a,point b){return a.x*b.y-a.y*b.x;} void graham(){ int i,j,k;sort(ai+1,ai+n+1,cmp); for (i=1;i<=n;++i){ while(m>1&&cross(zh[m]-zh[m-1],ai[i]-zh[m-1])<=0) --m; zh[++m]=ai[i]; }k=m; for (i=n-1;i;--i){ while(m>k&&cross(zh[m]-zh[m-1],ai[i]-zh[m-1])<=0) --m; zh[++m]=ai[i]; }--m; } void work(){ int i,j,l,r,up; for (i=1;i<m;++i){ up=(i==1?m-1:m);l=(i==1?m:i-1);r=up-1; for (j=up;j>=i+2;--j){ while((l<i||l>j+1)&&cross(zh[j]-zh[i],zh[l]-zh[i])<= cross(zh[j]-zh[i],zh[(l==1?m:l-1)]-zh[i])) l=(l==1?m:l-1); while(r>i+1&&cross(zh[r]-zh[i],zh[j]-zh[i])<=cross(zh[r-1]-zh[i],zh[j]-zh[i])) --r; ans=max(ans,cross(zh[j]-zh[i],zh[l]-zh[i])/2+cross(zh[r]-zh[i],zh[j]-zh[i])/2); } } } int main(){ int i,j;scanf("%d",&n); for (i=1;i<=n;++i) scanf("%lf%lf",&ai[i].x,&ai[i].y); graham();work();printf("%.3f\n",ans); }
bzoj1185 最小矩形覆盖
题目大意:给定一些点,求一个覆盖所有点的最小面积的矩形。
思路:求出凸包之后,至少有一条边和矩形的边是重合的,所以我们可以枚举这条边,然后旋转卡壳求出其他的三条边。对角线的点就是和这条边面积最大的点,其他两点可以用旋转的时候下一个点是否在直线的另一侧来判断。
注意:1)输出四个点的时候,满足y坐标最小(x坐标最小)的先输出,所以更新y坐标的时候,x坐标也要更新;
2)旋转卡壳的时候,如果相邻两点的参数(面积或者在直线某侧)一样,要到下一个点。
例子:8
0 0.1
0.1 0
0.9 0
1 0.1
1 0.9
0.9 1
0.1 1
0 0.9
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define N 200005 #define LD double #define eps 1e-9 #define inf 1e100 using namespace std; struct point{LD x,y;}ai[N],zh[N],po[4]; struct line{point p,v;}ans[5],ci[4]; int n,m;LD mn=inf; point operator+(point a,point b){return (point){a.x+b.x,a.y+b.y};} point operator-(point a,point b){return (point){a.x-b.x,a.y-b.y};} point operator*(point a,LD b){return (point){a.x*b,a.y*b};} LD cross(point a,point b){return a.x*b.y-a.y*b.x;} LD dot(point a,point b){return a.x*b.x+a.y*b.y;} LD sqr(LD x){return x*x;} LD lenth(point a){return sqrt(sqr(a.x)+sqr(a.y));} int cmp(LD x,LD y){ if (x-y>eps) return 1; if (y-x>eps) return -1; return 0;} int mcm(const point&a,const point&b){ return (cmp(a.x,b.x)==0 ? cmp(a.y,b.y)<0 : cmp(a.x,b.x)<0); } void graham(){ int i,k;m=0; sort(ai+1,ai+n+1,mcm); for (i=1;i<=n;++i){ while(m>1&&cmp(cross(zh[m]-zh[m-1],ai[i]-zh[m-1]),0.)<=0) --m; zh[++m]=ai[i]; }for (k=m,i=n-1;i;--i){ while(m>k&&cmp(cross(zh[m]-zh[m-1],ai[i]-zh[m-1]),0.)<=0) --m; zh[++m]=ai[i]; }for (--m,i=1;i<=m;++i) ai[i]=zh[i]; for (i=1;i<=m;++i) ai[i+m]=ai[i]; } void work(){ int i,j=0,a=0,b=0,k;LD mx,aa,bb; for (i=1;i<=m;++i){ j=max(j,i+1);mx=cross(ai[i+1]-ai[i],ai[j]-ai[i]); ci[0]=(line){ai[i],ai[i+1]-ai[i]}; while(j<i+m&&cmp(cross(ai[i+1]-ai[i],ai[j+1]-ai[i]),mx)>=0){ mx=cross(ai[i+1]-ai[i],ai[j+1]-ai[i]);++j; }aa=mx/lenth(ai[i+1]-ai[i]); a=max(i+1,a);b=max(j,b); ci[1]=(line){ai[a],(point){-ci[0].v.y,ci[0].v.x}}; while(a<j&&cmp(cross(ci[1].v,ai[a+1]-ai[a]),0.)<=0) ci[1].p=ai[++a]; ci[2]=(line){ai[j],(point){-ci[0].v.x,-ci[0].v.y}}; ci[3]=(line){ai[b],(point){-ci[1].v.x,-ci[1].v.y}}; while(b<i+m&&cmp(cross(ci[3].v,ai[b+1]-ai[b]),0.)<=0) ci[3].p=ai[++b]; bb=cross(ci[3].v,ci[1].p-ci[3].p)/lenth(ci[3].v); if (cmp(aa*bb,mn)<0){ mn=aa*bb; for (k=0;k<4;++k) ans[k]=ci[k]; } } } point gj(line a,line b){ LD k;point d=a.p-b.p; k=cross(b.v,d)/cross(a.v,b.v); return a.p+a.v*k;} int main(){ int i,j;LD my,mx;scanf("%d",&n); for (i=1;i<=n;++i) scanf("%lf%lf",&ai[i].x,&ai[i].y); graham();work(); if (cmp(mn,0.)==0) printf("%.5f\n",0.); else printf("%.5f\n",mn); ans[4]=ans[0];mx=my=inf; for (i=0;i<4;++i) po[i]=gj(ans[i],ans[i+1]); for (i=0;i<4;++i){ if (cmp(po[i].y,my)<0){my=po[i].y;mx=po[i].x;j=i;} else{ if (cmp(po[i].y,my)==0&&cmp(po[i].x,mx)<0){ mx=po[i].x;j=i; } } }for (i=0;i<4;++i){ if (cmp(po[(j+i)%4].x,0.)==0) printf("%.5f ",0.); else printf("%.5f ",po[(j+i)%4].x); if (cmp(po[(j+i)%4].y,0.)==0) printf("%.5f\n",0.); else printf("%.5f\n",po[(j+i)%4].y); } }
(五)其他
bzoj1504 Rainfall
题目大意:w宽的人行道上有n把伞,匀速直线往复运动,已知每把伞的长度速度和初始位置、降雨的单位体积,伞宽和人行道一样都是1m,落在伞上的雨会完全被吸收,求t时间后落在地面上的雨量。
思路:数据范围非常小,第一眼就是暴力,但是因为精度的问题wa了。画一个图,可以发现,以位置为x轴、时间为y轴,就变成了求一个不规则图形的面积,可以求出所有的关键点的时间(各种交点、折点的),然后对于相邻时间段内的图形,总能化成梯形的样子,所以面积就是(x1+x2)*(t1-t2)/2,最后答案*v就可以了。
注意:1)如果这把伞的长度是w并且有速度的话,要把速度变为0,不然会re;
2)答案可能是-0.000002之类的,会输出-0.00,这时候要判断一下。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 25 #define M 300 #define up 5000000 #define LD double #define per 1e-4 #define eps 1e-9 using namespace std; struct use{LD v,x,k,t;}ai[N]; struct point{ LD x,y; bool operator<(const point&xx)const{return x-xx.x<-eps;} }xi[N]; point operator+(point x,point y){return (point){x.x+y.x,x.y+y.y};} point operator-(point x,point y){return (point){x.x-y.x,x.y-y.y};} struct line{ point p,d;LD ti; }bi[N][M]; LD ci[up],w;int cn[N]={0},tot=0,n; int cmp(LD x,LD y){ if (x-y>eps) return 1; if (y-x>eps) return -1; return 0;} LD ab(LD x){return x<-eps ? -x : x;} LD cross(point x,point y){return x.x*y.y-x.y*y.x;} void getjiao(line x,line y){ point u=x.p-y.p; LD t=cross(y.d,u)/cross(x.d,y.d); if (cmp(t,x.ti)>0||cmp(t,0.)<0) return; u=y.p-x.p; t=cross(x.d,u)/cross(y.d,x.d); if (cmp(t,y.ti)>0||cmp(t,0.)<0) return; ci[++tot]=t + y.p.y; } LD calc(LD x){ int k,i,j;LD cnt=0.,la; for (k=1;k<=n;++k){ for (i=1;i<=cn[k]&&cmp(bi[k][i].p.y,x)<=0;++i);--i; xi[k].x=bi[k][i].p.x+bi[k][i].d.x*(x-bi[k][i].p.y); xi[k].y=xi[k].x+ai[k].t; }sort(xi+1,xi+n+1); for (la=0.,i=1;i<=n;++i){ if (xi[i].x>la) cnt+=xi[i].x-la; la=max(la,xi[i].y); }return cnt+w-la; } int main(){ int i,j,a,b;LD t,v,ans=0.,ct,cv; scanf("%d%lf%lf%lf",&n,&w,&t,&v); for (i=1;i<=n;++i){ scanf("%lf%lf%lf",&ai[i].x,&ai[i].t,&ai[i].v); bi[i][++cn[i]].p=(point){ai[i].x,0.}; bi[i+n][++cn[i+n]].p=(point){ai[i].x+ai[i].t,0.}; if (ai[i].t==w) ai[i].v=0.; if (cmp(ai[i].v,0.)==0){ bi[i][cn[i]].d=(point){0,1}; bi[i+n][cn[i+n]].d=(point){0,1}; bi[i][cn[i]].ti=bi[i+n][cn[i+n]].ti=t; bi[i][++cn[i]].p=(point){ai[i].x,t}; bi[i+n][++cn[i+n]].p=(point){ai[i].x+ai[i].t,t}; continue;} for (cv=ai[i].v,ct=0.;cmp(ct,t)<0;){ ct+=(bi[i][cn[i]].ti=bi[i+n][cn[i+n]].ti= min(t-ct,(cv>0 ? w-bi[i+n][cn[i+n]].p.x : bi[i][cn[i]].p.x)/ab(cv))); bi[i][cn[i]].d=(point){cv,1.}; bi[i+n][cn[i+n]].d=(point){cv,1.}; bi[i][++cn[i]].p=(point){bi[i][cn[i]-1].p.x+bi[i][cn[i]-1].ti*cv,ct}; bi[i+n][++cn[i+n]].p=(point){bi[i+n][cn[i+n]-1].p.x+bi[i+n][cn[i+n]-1].ti*cv,ct}; cv=-cv;ci[++tot]=ct; } }for (i=n*2;i;--i) for (j=1;j<cn[i];++j) for (a=n*2;a>i;--a) for (b=1;b<cn[a];++b){ if (cmp(cross(bi[i][j].d,bi[a][b].d),0.)==0) continue; getjiao(bi[i][j],bi[a][b]); } sort(ci+1,ci+tot+1);ci[0]=0.; for (i=1;i<=tot;i=j+1){ j=i;while(j<tot&&cmp(ci[j+1],ci[i])==0) ++j; ans+=(calc(ci[i])+calc(ci[i-1]))*(ci[i]-ci[i-1])/2.; }printf("%.2f\n",cmp(ans*v,0.)==0 ? 0 : ans*v); }
bzoj1074 折纸
题目大意:给定一张100*100的纸和n次折叠(将有向线段右边折到左边),询问最终图形中m个位置的层数(如果在某层边上就不算)。
思路:可以把询问位置求出所有原图中可能的位置(到这对每个操作做对称,要在有向线段的左边才能对称),然后对每个位置检验是否满足条件:1)在图形内;2)能折到初始位置(正着做一边对称)。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define N 10 #define M 1000 #define eps 1e-9 #define LD double using namespace std; int cmp(LD x,LD y){ if (x-y>eps) return 1; if (y-x>eps) return -1; return 0;} struct point{ LD x,y; bool operator<(const point&xx)const{ return (cmp(x,xx.x)==0 ? y+eps<xx.y : x+eps<xx.x); } }ai[2][M]; point operator+(point x,point y){return (point){x.x+y.x,x.y+y.y};} point operator-(point x,point y){return (point){x.x-y.x,x.y-y.y};} point operator*(point x,LD y){return (point){x.x*y,x.y*y};} bool operator==(point x,point y){return cmp(x.x,y.x)==0&&cmp(x.y,y.y)==0;} LD cross(point x,point y){return x.x*y.y-x.y*y.x;} LD dot(point x,point y){return x.x*y.x+x.y*y.y;} LD sqr(LD x){return x*x;} LD ab(LD x){return x<-eps ? -x : x;} LD lenth(point x){return sqrt(sqr(x.x)+sqr(x.y));} struct line{point p,d;}bi[M]; int n; point gj(line x,line y){ point u=x.p-y.p; LD t=cross(y.d,u)/cross(x.d,y.d); return x.p+x.d*t;} point gd(point x,line y,int k){ LD d=ab(cross(x-y.p,y.d))/lenth(y.d)*2; point u=(point){y.d.y*k/lenth(y.d),-y.d.x*k/lenth(y.d)}; return x+u*d;} bool onleft(point x,line y){return cross(x-y.p,y.d)<0;} bool judge(point u,point x){ int i,j; if (cmp(x.x,0.)<=0||cmp(x.x,100.)>=0||cmp(x.y,0.)<=0||cmp(x.y,100.)>=0) return false; for (i=1;i<=n;++i){ if (cmp(cross(x-bi[i].p,bi[i].d),0.)==0) return false; if (!onleft(x,bi[i])) x=gd(x,bi[i],-1); }return (x==u); } int calc(point u){ int i,j,cur=0,cnt[2]={0},ans; ai[cur][++cnt[cur]]=u; for (i=n;i;--i){ cnt[cur^=1]=0; for (j=1;j<=cnt[cur^1];++j) if (onleft(ai[cur^1][j],bi[i])){ ai[cur][++cnt[cur]]=gd(ai[cur^1][j],bi[i],1); ai[cur][++cnt[cur]]=ai[cur^1][j]; } }sort(ai[cur]+1,ai[cur]+cnt[cur]+1); for (ans=0,i=1;i<=cnt[cur];++i){ if (i>1&&ai[cur][i]==ai[cur][i-1]) continue; ans+=judge(u,ai[cur][i]); }return ans; } int main(){ int m,i,j;LD x1,y1,x2,y2; point xx;scanf("%d",&n); for (i=1;i<=n;++i){ scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2); bi[i].p=(point){x1,y1}; bi[i].d=(point){x2-x1,y2-y1}; }scanf("%d",&m); for (i=1;i<=m;++i){ scanf("%lf%lf",&x1,&y1); printf("%d\n",calc((point){x1,y1})); } }
bzoj1913 信号覆盖
题目大意:给出n个点,问任选三点做外接圆能覆盖的点的期望。(无三点共线和四点共圆)
思路:求所有三点外接圆覆盖点的个数/选三点的个数C(n,3)。考虑外接圆内部的点的个数,四边形中:1)凸四边形(一定有一对内角和>180度)会给答案贡献2;2)凹四边形会给答案贡献1(!!!)。而凸和凹四边形的总个数是一定的C(n,4),凹多边形个数sm比较好求,枚举凹四边形中间那个点x,其他点按和x的极角大小排序,其他三个点和x的连线夹角都小于180度,所以可以处理出每个点最多向后几个点<180度cnt,枚举第一个点a,单调b,对于c直接用cnt[b],但可能出现a和c夹角>180度的情况,要减掉(相当于在cnt[x]中选2个)。这样每个凹四边形会被统计3遍。最后答案就是(2*C(n,4)-sm+3*C(n,3))/C(n,3)。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define N 3005 #define LD double #define eps 1e-9 #define LL long long using namespace std; int cmp(LD x,LD y){ if (x-y>eps) return 1; if (y-x>eps) return -1; return 0;} struct use{ LD ang;int p; bool operator<(const use &x)const{return cmp(ang,x.ang)<0;} }bi[N]; int bz; LL cnt[N]; struct point{LD x,y;}ai[N]; point operator-(point a,point b){return (point){a.x-b.x,a.y-b.y};} LD cross(point a,point b){return a.x*b.y-a.y*b.x;} LL getc(int n,int m){ int i;LL cc=1LL; for (i=1;i<=m;++i) cc=cc*(LL)(n-m+i)/(LL)i; return cc;} int main(){ int n,i,j,k; LL ci,sm=0LL; point cc;scanf("%d",&n); for (i=1;i<=n;++i) scanf("%lf%lf",&ai[i].x,&ai[i].y); for (i=1;i<=n;++i){ for (bz=0,j=1;j<=n;++j){ if (i==j) continue; cc=ai[j]-ai[i]; bi[++bz]=(use){atan2(cc.y,cc.x),j}; }sort(bi+1,bi+bz+1); for (j=1;j<=bz;++j) bi[j+bz]=bi[j]; for (k=j=1;j<=bz;++j){ k=max(k,j);cc=ai[bi[j].p]-ai[i]; while((k+1)<=j+bz&&cmp(cross(cc,ai[bi[k+1].p]-ai[i]),0.)>0) ++k; cnt[j]=cnt[j+bz]=(LL)(k-j); }for (ci=0LL,k=0,j=1;j<=bz;++j){ if (k>=j) ci-=cnt[j]; k=max(k,j);cc=ai[bi[j].p]-ai[i]; while(k+1<=j+bz&&cmp(cross(cc,ai[bi[k+1].p]-ai[i]),0.)>0){ ++k;ci+=cnt[k]; }sm+=ci-cnt[j]*(cnt[j]-1LL)/2LL; } }sm/=3LL; printf("%.6f\n",(LD)(2LL*getc(n,4)-sm+3LL*getc(n,3))*1./(LD)getc(n,3)); }
bzoj4603 平凡的骰子(!!!)
题目大意:求多面体每面落地的概率。(一个面落地概率可以按照:从重心做单位球,对于一个面,这个面对应球上的凸面的面积/球的表面积是概率。球面上凸三角形的面积=a+b+c-pi,a、b、c表示三条边所在大圆两两二面角的弧度制大小。)
思路:分为两部分:求重心和算答案。求重心的部分可以四面体剖分,每个四面体的重心是四个顶点的坐标平均数,整个图形的重心是所有四面体重心的加权(体积)平均,四面体剖分的时候可以枚举一个顶点然后对于不过这个点的面做三角剖分得出四面体,四面体的体积是从一个点出去的三条棱的向量的混合积/6(认为三维向量叉积是法向量)(orz sunshine)。算答案的时候,求出每个点在单位球上对应的点,然后利用公式求每个面的面积(每个面三角剖分)。求二面角的时候可以用法向量。
注意:考场上求二面角的时候用的勾股定理之类的东西算得,求错了两个东西:(1)求垂直的时候是向同一条直线做垂直,所以垂足是从同一个点做出去的;(2)求垂足的时候用的勾股定理,会出现钝角三角形的情况,距离的时候要取负数,再用向量加减之类。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define LD double #define N 105 #define eps 1e-9 using namespace std; int cmp(LD x,LD y){ if (x-y>eps) return 1; if (y-x>eps) return -1; return 0;} struct point{LD x,y,z;}ai[N],gi,di[N]; struct mian{int k,nm[N];}bi[N]; LD ansi[N]; int n,f; point operator-(point a,point b){return (point){a.x-b.x,a.y-b.y,a.z-b.z};} point operator+(point a,point b){return (point){a.x+b.x,a.y+b.y,a.z+b.z};} point operator*(point a,LD b){return (point){a.x*b,a.y*b,a.z*b};} point operator/(point a,LD b){return (point){a.x/b,a.y/b,a.z/b};} point cross(point a,point b){return (point){a.y*b.z-a.z*b.y,a.z*b.x-a.x*b.z,a.x*b.y-a.y*b.x};} LD dot(point a,point b){return a.x*b.x+a.y*b.y+a.z*b.z;} LD sqr(LD x){return x*x;} LD dis(point a,point b){return sqrt(sqr(a.x-b.x)+sqr(a.y-b.y)+sqr(a.z-b.z));} LD ab(LD x){return (cmp(x,0.)<0 ? -x : x);} bool dun(point a,point b,point c){ return (cmp((sqr(dis(a,b))+sqr(dis(a,c))-sqr(dis(b,c)))/(2*dis(a,b)*dis(a,c)),0.)<0); } LD getj(point a,point b,point c){ LD d,h,dd,co;point aa,bb,cc; d=dis(a,b); h=sqrt(1-sqr(d/2.))*d; dd=sqrt(1-sqr(h)); if (dun(gi,a,b)) dd=-dd; aa=gi+(a-gi)*dd; d=dis(a,c); h=sqrt(1-sqr(d/2.))*d; dd=sqrt(1-sqr(h)); if (dun(gi,a,c)) dd=-dd; cc=gi+(a-gi)*dd; cc=c+(aa-cc); bb=b; co=(sqr(dis(bb,aa))+sqr(dis(cc,aa))-sqr(dis(bb,cc)))/(2.*dis(aa,bb)*dis(aa,cc)); return acos(co);} LD geta(point a,point b,point c){return getj(a,b,c)+getj(b,c,a)+getj(c,a,b)-M_PI;} LD calc(int x){ int i;LD dd,xx,yy,zz,ci=0.; for (i=1;i<=bi[x].k;++i){ dd=dis(ai[bi[x].nm[i]],gi); xx=gi.x+(ai[bi[x].nm[i]].x-gi.x)/dd; yy=gi.y+(ai[bi[x].nm[i]].y-gi.y)/dd; zz=gi.z+(ai[bi[x].nm[i]].z-gi.z)/dd; di[i]=(point){xx,yy,zz}; }for (i=2;i<bi[x].k;++i) ci+=geta(di[1],di[i],di[i+1]); return ci;} void getg(){ int i,j,k;point ci,a,b,c;LD ar,cnt=0.; gi=(point){0.,0.,0.}; for (i=1;i<=1;++i){ for (j=1;j<=f;++j){ for (k=1;k<=bi[j].k;++k) if (bi[j].nm[k]==i) break; if (k<=bi[j].k) continue; for (k=2;k<bi[j].k;++k){ a=ai[bi[j].nm[1]]; b=ai[bi[j].nm[k]]; c=ai[bi[j].nm[k+1]]; ci=(point){(a.x+b.x+c.x+ai[i].x)/4.,(a.y+b.y+c.y+ai[i].y)/4., (a.z+b.z+c.z+ai[i].z)/4.}; ar=ab(dot(cross(a-ai[i],b-ai[i]),c-ai[i])/6.); cnt+=ar; gi=gi+(ci*ar); } } }gi=gi/cnt; } int main(){ int i,j;scanf("%d%d",&n,&f); for(i=1;i<=n;++i) scanf("%lf%lf%lf",&ai[i].x,&ai[i].y,&ai[i].z); for (i=1;i<=f;++i){ scanf("%d",&bi[i].k); for (j=1;j<=bi[i].k;++j) scanf("%d",&bi[i].nm[j]); }getg(); for (i=1;i<=f;++i){ ansi[i]=calc(i); printf("%.7f\n",ansi[i]/(4*M_PI)); } }
bzoj2433 智能车比赛
题目大意:给出n个从左到右边上有重合的平行于坐标轴的矩形(xi,2=xi+1,1),给定S和T,问从S->T只经过矩形内部的最短路/速度。
思路:只会在顶点的地方改变方向,如果暴力建图,是O(n^3)。可以发现能互相转移的只有每条竖线上的四个点,其他的都是从左到右递推过去的,所以可以在竖线的时候暴力枚举更新,其他的O(n)扫过去更新,维护一个能更新的上下边界,如果在里面就可以从这个点直接过来,O(n^2)。
注意:有一些边界情况:1)S在T的右边,可以交换S和T走一遍;
2)删点的时候横坐标<=S.x的都可以删,横坐标>T.x的能删,因为可能T在左边界上,而T在上一个矩形外。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define N 10005 #define LD double #define eps 1e-9 #define inf 1e100 using namespace std; int cmp(LD x,LD y){ if (x-y>eps) return 1; if (y-x>eps) return -1; return 0;} int tt=0,n; LD dis[N]; struct point{LD x,y;int k;}ai[N],si,ti; int idx(int x,int y){return 4*(x-1)+y+2;} LD sqr(LD x){return x*x;} LD dk(point x,point y){ return cmp(x.x,y.x)==0 ? (cmp(x.y,y.y)>=0 ? -inf : inf) : (y.y-x.y)/(y.x-x.x); } LD uk(point x,point y){ return cmp(x.x,y.x)==0 ? (cmp(x.y,y.y)>=0 ? -inf : inf) : (y.y-x.y)/(y.x-x.x); } LD getd(point x,point y){return sqrt(sqr(x.x-y.x)+sqr(x.y-y.y));} LD work(){ int i,j,k,a,ss;LD up,low,ci; for (ss=1;cmp(ai[ss].x,si.x)<=0;++ss); for (;cmp(ai[tt].x,ti.x)>0;--tt); ai[--ss]=si;ai[++tt]=ti; for (i=ss;i<=tt;++i) dis[i]=inf; dis[ss]=0.; for (i=ss;i<=tt;i=j+1){ for (j=i;j<tt&&cmp(ai[j].x,ai[j+1].x)==0;++j); for (k=i;k<=j;++k) for (a=i;a<=j;++a) dis[a]=min(dis[a],dis[k]+getd(ai[a],ai[k])); for (k=i;k<=j;++k){ up=inf;low=-inf; for (a=k+1;a<=tt;++a){ if (!ai[a].k) low=max(low,ci=dk(ai[k],ai[a])); else up=min(up,ci=uk(ai[k],ai[a])); if (cmp(up,low)<0) break; if (cmp(ci,up)<=0&&cmp(ci,low)>=0) dis[a]=min(dis[a],dis[k]+getd(ai[k],ai[a])); } } }return dis[tt]; } int main(){ int i,j;LD x1,y1,x2,y2,vv;scanf("%d",&n); for (i=1;i<=n;++i){ scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2); ai[++tt]=(point){x1,y1,0}; ai[++tt]=(point){x1,y2,1}; ai[++tt]=(point){x2,y1,0}; ai[++tt]=(point){x2,y2,1}; }scanf("%lf%lf%lf%lf%lf",&si.x,&si.y,&ti.x,&ti.y,&vv); if (cmp(si.x,ti.x)>=0) swap(si,ti); printf("%.8f\n",work()/vv); }