Description
Input
Output
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 */