题目链接: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 }