[ACM_几何] Metal Cutting(POJ1514)半平面割与全排暴力切割方案
Description
In order to build a ship to travel to Eindhoven, The Netherlands, various sheet metal parts have to be cut from rectangular pieces of sheet metal. Each part is a convex polygon with at most 8 vertices. Each rectangular piece of sheet metal has width n and height m, so that the four corners of the sheet can be specified by the Cartesian coordinates (0, 0), (0, m), (n, m) and (n, 0) in clockwise order. The cutting machine available can make only straight-line cuts completely through the metal. That is, it cannot cut halfway through the sheet, turn, and then cut some more. You are asked to write a program to determine the minimum total length of cuts this machine has to make in order to cut out the polygon. The cuts must be along the edges of the poligon.
For example, if n = m = 100, and the polygon has vertices (80, 80), (70, 30), (20, 20) and (20, 80), the following diagram shows the optimal cut (the thick lines). The numbers show the order in which the cuts are made.
For example, if n = m = 100, and the polygon has vertices (80, 80), (70, 30), (20, 20) and (20, 80), the following diagram shows the optimal cut (the thick lines). The numbers show the order in which the cuts are made.
Input
The first line of input contains the two integers n and m where 0 < n, m <= 500. The next line contains p, the number of vertices in the polygon, where 3 <= p <= 8. Each of the next p lines contains two integers x and y where 0 < x < n and 0 < y < m, specifying the vertices of the polygon. The vertices are listed in clockwise order. You may assume that the polygon does not intersect itself, and that no three consecutive vertices are colinear.
Output
Print the minimum total length of cuts required to cut out the given polygon, accurate to 3 decimal places.
Sample Input
100 100 4 80 80 70 30 20 20 20 80
Sample Output
Minimum total length = 312.575
Source
题目大意:给你一个长为m宽为n的木板再给你一个凸的p边形的p个坐标点,求最短切割路径长度(这里割只能一刀子到头,不能停不能弯)。
Wrong啦:这题由于最多为8边形,所以果断采用暴力方法,枚举所有切割次序,注意这里的切割最好用半平面法来做,不然要考虑的情况特别多,其中我刚开始拿到这题就当成普通的几何问题来做,把每个割痕分为3部分l1、l2、l3再利用深搜动态的调整每一个割痕对应的l1/l2/l3的值,同时考虑边界切割问题和l1或l3为0的情况,结果都不能过,最后发现少考虑了一种最坑的情况,即:非常大木板与非常小正六边形问题(这里会产生新割痕对旧割痕的影响,所以不得不换用另一种思路!
1 #include<iostream> 2 #include<math.h> 3 #include<cstdio> 4 #include<algorithm> 5 using namespace std; 6 class point{ 7 public: 8 double x,y; 9 bool in(int n,int m){ 10 return (x>=0 && n>=x && y>=0 && m>=y); 11 } 12 }; 13 void chose(point a1,point a2,point a3,point a4,point &a5,point &a6,int n,int m){ 14 bool ok=0; 15 if(a1.in(n,m))a5.x=a1.x,a5.y=a1.y,ok=1; 16 if(a2.in(n,m)){ 17 if(ok){a6.x=a2.x;a6.y=a2.y;return;} 18 else a5.x=a2.x,a5.y=a2.y,ok=1; 19 } 20 if(a3.in(n,m)){ 21 if(ok){a6.x=a3.x;a6.y=a3.y;return;} 22 else a5.x=a3.x,a5.y=a3.y,ok=1; 23 } 24 if(a4.in(n,m))a6.x=a4.x,a6.y=a4.y; 25 } 26 double dis(point a,point b){ 27 return sqrt(1.0*(b.x-a.x)*(b.x-a.x)+(b.y-a.y)*(b.y-a.y)); 28 } 29 class line{ 30 public: 31 double l1,l2,l3; 32 int num; 33 bool set(point a,point b,int n,int m){ 34 if(fabs(a.y-b.y)<0.00001){//横着的 35 if(fabs(a.y-m)<0.00000001 || fabs(a.y)<0.00000001){l1=l3=l2=1000000000;return 0;} 36 else if(b.x>a.x){l1=a.x;l2=b.x-a.x;l3=n-b.x;} 37 else {l1=n-a.x;l2=a.x-b.x;l3=b.x;} 38 }else if(fabs(a.x-b.x)<0.00001){//竖着的 39 if(fabs(a.x-n)<0.00000001 || fabs(a.x)<0.00000001){l1=l3=l2=1000000000;return 0;} 40 else if(a.y>b.y){l1=m-a.y;l2=a.y-b.y;l3=b.y;} 41 else {l1=a.y;l2=b.y-a.y;l3=m-b.y;} 42 }else{//其他情况 43 l2=sqrt(1.0*(b.x-a.x)*(b.x-a.x)+(b.y-a.y)*(b.y-a.y)); 44 double k=(b.y-a.y)/(b.x-a.x); 45 point PJ[7]; 46 PJ[0].x=0;PJ[0].y=a.y-a.x*k; 47 PJ[1].x=a.x-a.y/k;PJ[1].y=0; 48 PJ[2].x=n;PJ[2].y=a.y+(n-a.x)*k; 49 PJ[3].x=a.x+(m-a.y)/k;PJ[3].y=m; 50 /*for(int i=0;i<4;i++) 51 cout<<PJ[i].x<<' '<<PJ[i].y<<'\n';*/ 52 chose(PJ[0],PJ[1],PJ[2],PJ[3],PJ[4],PJ[5],n,m); 53 //cout<<PJ[4].x<<' '<<PJ[4].y<<' '<<PJ[5].x<<' '<<PJ[5].y<<'\n'; 54 if(PJ[4].x>PJ[5].x){ 55 PJ[6]=PJ[4]; 56 PJ[4]=PJ[5]; 57 PJ[5]=PJ[6]; 58 } 59 if(a.x<b.x){//a在左 60 l1=dis(PJ[4],a); 61 l3=dis(PJ[5],b); 62 }else{ 63 l1=dis(a,PJ[5]); 64 l3=dis(b,PJ[4]); 65 } 66 }return 1; 67 } 68 69 }; 70 bool operator<(line L1,line L2){ 71 return (L1.l1+L1.l3)<(L2.l1+L2.l3); 72 } 73 74 //--------------------------------------- 75 line L[8]; 76 int p,lose; 77 int vis[8]; 78 double minRoad,proRoad; 79 void dfs(int c){ 80 bool ok=0; 81 for(int i=0;i<p-lose;i++)if(!vis[i]){ 82 int cur=L[i].num; 83 int pre=(cur+7)%p;double prel3; 84 int next=(cur+1)%p;double nextl1; 85 86 proRoad+=(L[i].l1+L[i].l2+L[i].l3);vis[i]=1; 87 for(int j=0;j<p;j++){//减掉L[c]对其前后的影响 88 if(L[j].num==pre){prel3=L[j].l3;L[j].l3=0;} 89 else if(L[j].num==next){nextl1=L[j].l1;L[j].l1=0;} 90 } 91 dfs(i); 92 proRoad-=(L[i].l1+L[i].l2+L[i].l3);vis[i]=0; 93 for(int j=0;j<p;j++){//恢复L[c]对其前后的影响 94 if(L[j].num==pre)L[j].l3=prel3; 95 else if(L[j].num==next)L[j].l1=nextl1; 96 } 97 ok=1; 98 } 99 if(!ok){//没有要剪得,表明已经剪完 100 if(proRoad<minRoad)minRoad=proRoad; 101 } 102 } 103 void Solve(){ 104 minRoad=10000000000000;proRoad=0; 105 for(int i=0;i<8;i++)vis[i]=0; 106 for(int i=0;i<p-lose;i++){ 107 int cur=L[i].num; 108 int pre=(cur+7)%p;double prel3; 109 int next=(cur+1)%p;double nextl1; 110 111 proRoad+=(L[i].l1+L[i].l2+L[i].l3);vis[i]=1; 112 for(int j=0;j<p;j++){//减掉L[c]对其前后的影响 113 if(L[j].num==pre){prel3=L[j].l3;L[j].l3=0;} 114 else if(L[j].num==next){nextl1=L[j].l1;L[j].l1=0;} 115 } 116 dfs(i); 117 proRoad-=(L[i].l1+L[i].l2+L[i].l3);vis[i]=0; 118 for(int j=0;j<p;j++){//恢复L[c]对其前后的影响 119 if(L[j].num==pre)L[j].l3=prel3; 120 else if(L[j].num==next)L[j].l1=nextl1; 121 } 122 } 123 } 124 //------------------------------------- 125 int main(){ 126 int T;cin>>T;int ok=0; 127 while(T--){ 128 int n,m;cin>>n>>m; 129 cin>>p; 130 point P[8]; 131 for(int i=0;i<p;i++)cin>>P[i].x>>P[i].y; 132 lose=0; 133 for(int i=0;i<p;i++){ 134 lose+=!L[i].set(P[i],P[(i+1)%p],n,m); 135 L[i].num=i; 136 } 137 /*for(int i=0;i<p;i++){ 138 cout<<L[i].l1<<' '<<L[i].l2<<' '<<L[i].l3<<'\n'; 139 }*/ 140 sort(L,L+p); 141 /*for(int i=0;i<p;i++){ 142 cout<<L[i].l1<<' '<<L[i].l2<<' '<<L[i].l3<<'\n'; 143 }*/ 144 145 /*double sum=0; 146 for(int i=0;i<p-lose;i++){ 147 sum+=(L[0].l1+L[0].l2+L[0].l3); 148 L[0].l1=10000000; 149 int cur=L[0].num; 150 int pre=(cur+7)%p; 151 int next=(cur+1)%p; 152 for(int j=0;j<p;j++){ 153 if(L[j].num==pre)L[j].l3=0; 154 else if(L[j].num==next)L[j].l1=0; 155 } 156 sort(L,L+p); 157 //for(int i=0;i<p;i++){ 158 // cout<<L[i].l1<<' '<<L[i].l2<<' '<<L[i].l3<<'\n'; 159 //} 160 }*/////贪心求法 161 Solve();//深搜求法 162 if(minRoad==10000000000000)minRoad=0; 163 if(ok)printf("\n");ok=1; 164 printf("Minimum total length = %.3lf\n",minRoad); 165 } 166 }
半平面法:最后想到了半平面的方法,就是把木板看成一个4个顶点的凸包,切出里面的p多边形即依次枚举切割顺序,对于每一次切割肯定沿着某一条边,这样最多8!种情况。然后每一次切割我用点集st[]维护每次切过后剩下的部分(新的凸包),np维护新凸包的顶点数。这里的维护就采用了半平面割的方法:
1 #include <cmath> 2 #include <cstdio> 3 #include<algorithm> 4 using namespace std; 5 const int maxn = 100; 6 7 8 double min(double a,double b){return a<b?a:b;}//求2个double中较小的一个 9 10 const double eps = 1e-8; 11 double sgn(double x) {return fabs(x)<eps?0:(x>0?1:-1);}//经典比较2个double类数的方法,相等0,大于1,小于-1 12 //------------------------------------------------------------------ 13 //2维几何模板 14 //------------------------------------------------------------------ 15 struct Point{//点类(x,y)构造函数+==重定义 16 double x,y; 17 Point(double tx=0,double ty=0){x=tx;y=ty;} 18 bool operator == (const Point& t) const { 19 return sgn(x-t.x)==0 && sgn(y-t.y)==0; 20 } 21 }p[maxn],Set[maxn],st[maxn],tmp[maxn],pp[maxn]; 22 23 double dist(Point a,Point b){return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));}//a、b两点的距离 24 double cross(Point a,Point b,Point c){return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);}//向量ab和向量ac的差积 25 26 struct Seg{Point s,e;};//射线se 27 bool outside(Seg seg,Point p){return cross(seg.s,seg.e,p)>eps;}//点p在射线seg左 28 bool inside(Seg seg,Point p){return cross(seg.s,seg.e,p)<-eps;}//点p在射线seg右 29 30 Point Intersect(Point p1, Point p2, Point p3, Point p4, Point& p) { 31 double a1, b1, c1, a2, b2, c2, d; 32 a1 = p1.y - p2.y; b1 = p2.x - p1.x; c1 = p1.x*p2.y - p2.x*p1.y; 33 a2 = p3.y - p4.y; b2 = p4.x - p3.x; c2 = p3.x*p4.y - p4.x*p3.y; 34 d = a1*b2 - a2*b1; 35 if ( fabs(d) < eps ) return false; 36 p.x = (-c1*b2 + c2*b1) / d; 37 p.y = (-a1*c2 + a2*c1) / d; 38 return p; 39 }//直线p1p2和p3p4的交点存在p中(当且仅当Cross(p1p2,p3p4)非0) 40 //----------------------------------------------------------------- 41 double W,H; 42 int a[10],n,pn; 43 double CUT(Seg seg,Point p[]){ 44 int i,j,tot=0; 45 Point A,B; 46 A=B=Point(0,0); 47 bool s,e; 48 for(i=0;i<pn;i++){//这里A、B交替存储seg与动态凸包p[]的交点,并把新凸包保存在pp中,新的凸包点数保存在tot中 49 if(!outside(seg,p[i]))pp[tot++]=p[i];//p[i]在射线seg右或上 50 else { 51 if(i==0&&!outside(seg,p[pn-1])){//当前p[i]p[i-1]和seg的交点(当i==0时要特殊处理一下) 52 B=A; 53 pp[tot++]=Intersect(seg.s,seg.e,p[i],p[pn-1],A); 54 } 55 if(i!=0&&!outside(seg,p[i-1])) { 56 B=A; 57 pp[tot++]=Intersect(seg.s,seg.e,p[i],p[i-1],A); 58 } 59 if(!outside(seg,p[i+1])) {//当前p[i]p[i+1]和seg的交点(因为我们已经令p[最后一个的后一个]=p[0]所以不必特殊处理) 60 B=A; 61 pp[tot++]=Intersect(seg.s,seg.e,p[i],p[i+1],A); 62 } 63 } 64 } 65 pp[tot]=pp[0];//特殊处理尾部 66 pn=tot;memcpy(st,pp,sizeof(pp));//更新p[]和pn 67 return dist(A,B);//返回割痕长度 68 } 69 int main(){ 70 int i; 71 while(scanf("%lf%lf",&W,&H)!=EOF){ 72 double ans=1e20; 73 st[4]=st[0]=Point(0,0);//tmp[100]是用来保存原来矩形凸包外四个顶点的, 74 st[1]=Point(0,H); //st[100]是切割过程中凸包的顶点 75 st[2]=Point(W,H); //因此对于每种切割方案,刚开始都要将st设为原始矩形凸包,这也是tmp存在的原因 76 st[3]=Point(W,0); 77 memcpy(tmp,st,sizeof(st)); 78 scanf("%d",&n);Seg ts[100]; 79 for(i=0;i<n;i++) { 80 scanf("%lf%lf",&p[i].x,&p[i].y); 81 a[i]=i;//0、1、2.....后面对其全排枚举实现所有切割方法的暴力枚举 82 }p[n]=p[0]; 83 for(i=0;i<n;i++) ts[i].s=p[i],ts[i].e=p[i+1]; 84 do{ 85 double tlen=0;//切割长度 86 memcpy(st,tmp,sizeof(tmp)); 87 pn=4;//pn和st[100]一样是计算过程中的量(对于每种切割方案,其开始要更新为原来的,其过程要变化,pn即凸包st的点数) 88 for(i=0;i<n;i++){//按照获得的全排序列切割 89 tlen+=CUT(ts[a[i]],st); 90 } 91 ans=min(ans,tlen);//求出最小值,保存在ans里 92 }while(next_permutation(a,a+n));//暴力枚举所有情况next_permutation(a,a+n)是将数组全排列找出 93 printf("Minimum total length = %.3lf\n",ans); 94 } 95 return 0; 96 } 97 /* 98 线的交点 99 1>直线可以用直线上一点P0和方向向量v来表示:直线上所有点P满足P=P0+t*v,其中t为参数;如果已知直线上的2个不同的点A、B,则方向向量 100 为B-A,所以参数方程为A+(B-A)*t;参数方程可以方便的表示出直线射线和线段,区别仅在于t的范围:直线t无范围,射线t>0,线段t在0~1之间 101 2>直线交点:设直线分别为P+t*v,Q+t*w,u=PQ,交点在第一条直线上的参数为t1、在第二条直线上的参数为t2,则x、y的坐标可以列出一个方程, 102 截得:t1=cross(w,u)/cross(v,w),t2=cross(v,u)/cross(v,w),前提要确保分母不为0!! 103 */ 104 /* 105 在STL中,除了next_permutation外,还有一个函数prev_permutation,两者都是用来计算排列组合的函数。 106 前者是求出下一个排列组合,而后者是求出上一个排列组合。所谓“下一个”和“上一个”,书中举了一个 107 简单的例子:对序列 {a, b, c},每一个元素都比后面的小,按照字典序列,固定a之后,a比bc都小,c比b大, 108 它的下一个序列即为{a, c, b},而{a, c, b}的上一个序列即为{a, b, c},同理可以推出所有的六个序列为: 109 {a, b, c}、{a, c, b}、{b, a, c}、{b, c, a}、{c, a, b}、{c, b, a},其中{a, b, c}没有上一个元素, 110 {c, b, a}没有下一个元素。 111 */