题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6617
题目大意:给出一凸包(P),求最小的与(P)相似且对应边平行的多边形,使得题目给出的(m)个点(q_i)都被该多边形包含在内,输出最小相似比
题解:二分答案(k),考虑如何判断(P)被放大(k)倍后是否可以通过平移这(m)个点使他们都在多边形内。将多边形的所有边看成有向线段(逆时针),则(m)个点都在多边形内当且仅当他们都在这些有向线段的左侧。对第(i)条边记录(f_i)表示如果点(q_{f_i})在这条边左侧,就能保证所有点都在其左侧,通过对这(m)个点求凸包后可以在(O(m))的时间复杂度内求出(f_i)。这样我们就只需要知道,是否存在一个平移的向量,使得这(m)个点平移之后,所有的点(q_{f_i})都在第(i)条边的左侧。
于是我们如果对每条边((p_i,p_{i+1})),求出了符合条件的向量集(vec{A_i}),就可以通过判断他们的交集是否为空来判断当前的比例(k)是否合法。可以发现(vec{A_i}={vec{OA}|A ext{ is on the left of }vec{(p_i-q_{f_i},p_{i+1}-q_{f_i})}})代表了一个半平面,这时我们就可以通过求半平面交来解决这道题了。
注意题目的要求是对应边平行,因此我们还要把原图形旋转(180^{circ})再做一遍,取两次答案的最小值输出。
#include<bits/stdc++.h> using namespace std; #define N 100001 const double eps=1e-10; int sgn(double x) { if(x<-eps)return -1; if(x>eps)return 1; return 0; } struct Point { double x,y; void read(){scanf("%lf%lf",&x,&y);} Point operator +(const Point &t)const{return {x+t.x,y+t.y};} Point operator -(const Point &t)const{return {x-t.x,y-t.y};} double operator *(const Point &t)const{return x*t.y-y*t.x;} Point operator *(const double t)const{return {x*t,y*t};} Point operator /(const double t)const{return {x/t,y/t};} double ang()const{return atan2(1.0*y,1.0*x);} double length()const{return sqrt(x*x+y*y);} }p[N]; struct Line { Point p1,p2; Point isct(const Line &t)const { double a=(p2-p1)*(t.p1-p1); double b=(p2-p1)*(p1-t.p2); return (t.p1*b+t.p2*a)/(a+b); } }q[N],line[N]; Point cent; bool cmpang(const Point &p1,const Point &p2) { int tmp=sgn((p1-cent).ang()-(p2-cent).ang()); if(tmp!=0)return tmp<0; return (p1-cent).length()<(p2-cent).length(); } struct Polygon { int n; Point a[N]; void read() { scanf("%d",&n); for(int i=1;i<=n;i++) a[i].read(); } void ChangetoConvex() { for(int i=2;i<=n;i++) if(a[i].x<a[1].x || (a[i].x==a[1].x && a[i].y<a[1].y)) swap(a[1],a[i]); cent=a[1]; sort(a+2,a+n+1,cmpang); int top=2; for(int i=3;i<=n;i++) { while(top>=2 && (a[top]-a[top-1])*(a[i]-a[top])<=0)top--; a[++top]=a[i]; } n=top; } }P,Q; int T,nxt[N],pre[N],f[N]; bool check(int i,Point A,Point B) { return sgn((B-A)*(Q.a[pre[i]]-Q.a[i]))>=0 && sgn((B-A)*(Q.a[nxt[i]]-Q.a[i]))>=0; } bool Left(const Point &p,const Line &l){return sgn((l.p2-l.p1)*(p-l.p1))==1;} bool HalfPlane(int n) { int h=1,t=1; q[1]=line[1]; for(int i=2;i<=n;i++) { while(h<t && !Left(p[t-1],line[i]))t--; while(h<t && !Left(p[h],line[i]))h++; if(sgn((q[t].p2-q[t].p1)*(line[i].p2-line[i].p1))==0) q[t]=Left(q[t].p1,line[i])?q[t]:line[i]; else q[++t]=line[i]; if(h<t)p[t-1]=q[t].isct(q[t-1]); } while(h<t && !Left(p[t-1],q[h]))t--; if(t-h<=1)return false; p[t]=q[t].isct(q[h]); double ans=0; for(int i=h;i<=t;i++) ans+=p[i]*p[(i+1-h)%(t-h+1)+h]; return sgn(ans)>=0; } bool check(double k) { for(int i=1;i<=P.n;i++) line[i]={P.a[i]*k-Q.a[f[i]],P.a[i%P.n+1]*k-Q.a[f[i]]}; return HalfPlane(P.n); } double solve() { int j=1; for(int i=1;i<=P.n;i++) { while(!check(j,P.a[i],P.a[i%P.n+1]))j=nxt[j]; f[i]=j; } double l=0,r=1e9,mid; while(l+eps<r) { mid=(l+r)*0.5; if(check(mid))r=mid; else l=mid; } return l; } void init() { double ans=1e18; P.read(),Q.read(); if(Q.n==1){printf("%.6f ",0.0);return;} P.ChangetoConvex(); Q.ChangetoConvex(); for(int i=1;i<=Q.n;i++) nxt[i]=i%Q.n+1,pre[i%Q.n+1]=i; ans=min(ans,solve()); for(int i=1;i<=P.n;i++) P.a[i]=P.a[i]*(-1.0); ans=min(ans,solve()); printf("%.6f ",ans); } int main() { scanf("%d",&T); while(T--)init(); }
关于(vec{A_i})的说明:设点(q_{f_i})为(Q),则(vec{OA})满足原点(O)在平移(vec{OA})后,他在有向线段(vec{(p_i-Q,p_{i+1}-Q)})的左侧。所以原点(O)在平移(vec{OA})后,再平移(vec{OQ}),就能满足其在有向线段(vec{(p_i,p_{i+1})})的左侧。而(O+vec{OA}+vec{OQ}=O+vec{OQ}+vec{OA}=Q+vec{OA}),因此将(Q)平移(vec{OA})是能满足要求的。