【题解】切割多边形 [SCOI2003] [P4529] [Bzoj1091]
【题解】切割多边形 [SCOI2003] [P4529] [Bzoj1091]
传送门:切割多边形 \(\text{[SCOI2003] [P4529]}\) \(\text{[Bzoj1091]}\)
【题目描述】
给出一个 \(Mx*My\) \((0 < Mx,My < 500)\) 的矩形,现要用 \(n\) \((3 \leqslant n \leqslant 8)\) 条直线依次对其进行切割,将它变成凸 \(n\) 边形,每次切割的长度为该直线在剩下的矩形内部的部分的长度,求最短的切割线总长度。
【分析】
一道计算几何膜您题。
由于 \(n\) 比较小,可以 \(O(n!)\) 枚举切割线的顺序,最后计算总长度取最小值就行了。
但有个非常麻烦的问题:如何求每次加入直线的切割线长度?
考虑用一个栈储存当前已经加入的直线(上下左右四个边界会在最初时加入),如果现在要加入直线 \(p_1-p_2\)(这里为方便描述,设 \(p_1\) 在左边,\(p_2\) 在右边),先求出它与栈中所有直线的交点,然后在这些交点中分别找到:\(p_1\) 左边距离 \(p_1\) 最近的点 \(ans_1\) 和 \(p_2\) 右边距离 \(p_2\) 最近的点\(ans_2\),易知 \(len(ans_1,ans_2)\) 即为当次切割线长度。
图就不画了,自己领会吧。。。
【Code】
#include<algorithm>
#include<cstdio>
#include<cmath>
#define LD double
#define LL long long
#define Re register int
#define Vector Point
using namespace std;
const int N=20;
const LD eps=1e-9,inf=1e9;
inline int dcmp(LD a){return a<-eps?-1:(a>eps?1:0);}//处理精度
inline LD Abs(LD a){return a*dcmp(a);}//取绝对值
struct Point{
LD x,y;Point(LD X=0,LD Y=0){x=X,y=Y;}
inline void in(){scanf("%lf%lf",&x,&y);}
inline void out(){printf("%.2lf %.2lf\n",x,y);}
}p1,p2,p3,p4,P[N];
struct Line{
Point a,b;int id;LD k;Line(Point X=Point(0,0),Point Y=Point(0,0),int ID=0,LD K=0){a=X,b=Y,id=ID,k=K;}
inline void sakura(){k=(!dcmp(a.x-b.x))?0.0:(a.y-b.y)/(a.x-b.x);}
inline bool operator<(Line O)const{return k<O.k;}
}L[N],Q[N];
inline LD Dot(Vector a,Vector b){return a.x*b.x+a.y*b.y;}//点积
inline LD Cro(Vector a,Vector b){return a.x*b.y-a.y*b.x;}//叉积
inline LD Len(Vector a){return sqrt(Dot(a,a));}//模长
inline Point operator+(Point a,Vector b){return Vector(a.x+b.x,a.y+b.y);}
inline Vector operator-(Point a,Point b){return Vector(a.x-b.x,a.y-b.y);}
inline Vector operator*(Vector a,LD b){return Vector(a.x*b,a.y*b);}
inline Point cross_LL(Point a,Point b,Point c,Point d){//两直线AB,CD的交点
Vector x=b-a,y=d-c,z=a-c;
return a+x*(Cro(y,z)/Cro(x,y));//点A加上向量AF
}
int n,t,a[N],vis[N];LD Mx,My,Ans=inf;
inline void dfs(Re g,LD ans){
if(g>n){Ans=min(Ans,ans);return;}
for(Re i=1;i<=n;++i)
if(!vis[i]){
Point p1=L[i].a,p2=L[i].b,ans1=Point(inf,inf),ans2=Point(inf,inf);
//ans1:在p2-p1延长线上距离p1最近的点
//ans2:在p1-p2延长线上距离p2最近的点
for(Re j=1;j<=t;++j){
Point b=cross_LL(p1,p2,Q[j].a,Q[j].b);//获取直线L[i]与Q[i]的交点b
if(dcmp(Len(p1-b)-Len(p2-b))<0&&dcmp(Len(p1-ans1)-Len(p1-b))>0)ans1=b;//如果 len(b,p1)<len(b,p2) 且 len(p1,b)<len(p1,ans1)
if(dcmp(Len(p2-b)-Len(p1-b))<0&&dcmp(Len(p2-ans2)-Len(p2-b))>0)ans2=b;//如果 len(b,p2)<len(b,p1) 且 len(p2,b)<len(p2,ans2)
}
vis[i]=1,Q[++t]=L[i];
dfs(g+1,ans+Len(ans1-ans2));//加上len(ans1,ans2)
vis[i]=0,--t;
}
}
int main(){
// freopen("123.txt","r",stdin);
scanf("%lf%lf%d",&Mx,&My,&n);
for(Re i=1;i<=n;++i)P[i].in();
for(Re i=1;i<=n;++i)L[i]=Line(P[i],P[i<n?i+1:1],i);//获取n条直线
p1=Point(0,0),p2=Point(0,My),p3=Point(Mx,My),p4=Point(Mx,0);//四个顶点
Q[++t]=Line(p1,p2),Q[++t]=Line(p2,p3),Q[++t]=Line(p3,p4),Q[++t]=Line(p4,p1);//先将四个边界入队
dfs(1,0.0);
printf("%.3lf\n",Ans);
}