The Fortified Forest POJ - 1873 (1999 WF,凸包+二进制枚举)
题目链接:https://vjudge.net/problem/POJ-1873
题意:传说中WF水题。。。。给你树的每个坐标,价值以及建造长度,要求你砍掉一些树来把其他数都围起来,来求砍掉哪些树和多余的砍掉的木材长度,达到最优价值最少
思路:用二进制枚举所有砍树的情况(2^n),然后算出没被砍的树的凸包和周长,当砍掉树的建造周长大于等于凸包周长时,记录价值,当下一次价值大于该价值时可以直接跳过不用考虑,每次符合条件更新即可
1 // 2 // Created by HJYL on 2020/2/1. 3 // 4 #include<iostream> 5 #include<cstring> 6 #include<cstdio> 7 #include<cmath> 8 #include<algorithm> 9 using namespace std; 10 const double eps=1e-8; 11 const int maxn=100; 12 const int INF=0x3ffff; 13 int dcmp(double x) 14 { 15 if(fabs(x)<eps) 16 return 0; 17 return x<0?-1:1; 18 } 19 struct Point { 20 double x, y; 21 int value; 22 int len; 23 24 Point(double x = 0, double y = 0) : x(x), y(y) {} 25 26 Point operator-(Point const &B) const { 27 return Point(x - B.x, y - B.y); 28 } 29 bool operator<(Point const &C)const{ 30 if(x==C.x) 31 return y<C.y; 32 return x<C.x; 33 } 34 }initial,p[maxn]; 35 36 double Cross(Point A,Point B) 37 { 38 return (A.x*B.y)-(A.y*B.x); 39 } 40 41 double Len(Point A,Point B) 42 { 43 return sqrt((A.x-B.x)*(A.x-B.x)+(A.y-B.y)*(A.y-B.y)); 44 } 45 //极角排序 46 bool cmp(const Point& A,const Point& B) 47 { 48 double t=Cross(A-initial,B-initial); 49 if(t>0)//叉积大于0,B在A的左边,A在外凸包上 50 return true; 51 else if(t<0) 52 return false; 53 else 54 return Len(A,initial)<Len(B,initial);//极角相同采用最近的点 55 } 56 int Hull(Point *p,int n,Point *ch) ///**求凸包*/ 57 { 58 sort(p,p+n);//n顶点数 59 int m = 0; 60 for(int i = 0; i < n; i++) 61 { 62 while(m > 1 && Cross(ch[m-1]-ch[m-2], p[i]-ch[m-2]) <= 0) m--; 63 ch[m++] = p[i]; 64 } 65 int k = m; 66 for(int i = n-2; i >= 0; i--) 67 { 68 while(m > k && Cross(ch[m-1]-ch[m-2], p[i]-ch[m-2]) <= 0) m--; 69 ch[m++] = p[i]; 70 } 71 if(n > 1) m--; 72 return m; 73 } 74 75 int main() 76 { 77 int n; 78 int cas=1; 79 while(~scanf("%d",&n)&&n) 80 { 81 for(int i=0;i<n;i++) 82 scanf("%lf%lf%d%d",&p[i].x,&p[i].y,&p[i].value,&p[i].len); 83 int minv=INF;//最优花费价值价值 84 int cut=INF;//砍掉树的最优数目 85 double morel=0;//剩余木材长度 86 int ops=0;//选择的所有二进制中第几个 87 for(int i=0;i<(1<<n);i++)//二进制枚举所有情况 88 { 89 double ll=0;//选择树的长度和 90 double vv=0;//选择树的价值和 91 int pos=0;//没被选的树的数量 92 Point bb[maxn],ch[maxn*2];//bb记录没被选的,ch用来求凸包顶点集存坐标 93 int cut1;//该种二进制方法下砍掉树的数量 94 for(int j=0;j<n;j++)//一种二进制中可选与不可选 95 { 96 if(i&(1<<j))//砍掉该树 97 { 98 ll+=p[j].len; 99 vv+=p[j].value; 100 } else//不砍的树 101 { 102 bb[pos].x=p[j].x; 103 bb[pos++].y=p[j].y;//存不砍树的点集接下来求凸包 104 } 105 } 106 // printf("pos=%d\n",pos); 107 if(vv>minv)//这种方法利用的价值大于之前的不可取 108 continue; 109 cut1=n-pos;//砍掉的数目 110 double zhou=0; 111 int mm=Hull(bb,pos,ch);//凸包顶点数 112 // printf("mm=%d\n",mm); 113 // printf("ch=\n"); 114 // for(int kk=0;kk<mm;kk++) 115 // printf("ch%d=(%lf,%lf)\n",kk,ch[kk].x,ch[kk].y); 116 for(int t=1;t<mm;t++) 117 zhou+=Len(ch[t],ch[t-1]); 118 zhou+=Len(ch[0],ch[mm-1]);//凸包周长 119 // printf("zhou=%.2lf\n",zhou); 120 if(ll>=zhou) 121 { 122 if(vv<minv||(vv==minv&&cut1<cut))//价值小于之前的或者价值相等但是数目比之前的还要少则更优 123 { 124 minv=vv; 125 cut=cut1; 126 morel=ll-zhou; 127 ops=i; 128 } 129 } 130 } 131 // printf("ops=%d\n",ops); 132 if(cas>1) 133 printf("\n"); 134 printf("Forest %d\n",cas++); 135 printf("Cut these trees:"); 136 for(int tt=0;tt<n;tt++) 137 if(ops&(1<<tt))//该种二进制中被砍的树的位置(编号) 138 printf(" %d",tt+1); 139 printf("\n"); 140 printf("Extra wood: %.2lf\n",morel); 141 } 142 return 0; 143 }