USACO算法系列四十一——fc

     题目:http://www.nocow.cn/index.php/Translate:USACO/fc

    求所有点的凸包的最小周长。

    利用的是“卷包裹”算法:http://www.nocow.cn/index.php/Graham_Scan

    算法描述如下:

   

  • 找出一个必定会在凸包内的中点
  • 计算每个点和中点的连线与x轴的夹角(在 0——360 度的范围内)
  • 根据这些夹角对顶点排序
  • 加入最初的两个顶点
  • 对于除最后一个顶点以外的其余顶点
  • 让其成为凸包上的下一个顶点
  • 检查它和前面两个顶点组成的角是否大于 180 度
    • 如果它和前面两个顶点组成的角大于 180 度,那么把它前面那个顶点删掉
  • 加入最后一个顶点
    • 完成上述的删除任务
    • 检查最后一个顶点和它的前一个顶点和第一个顶点所组成的角是否大于 180 度,或者最后一个顶点和第一、第二个顶点组成的角是否大于 180 度。
    • 如果第一种情况为真,删除最后一个顶点,并且检查倒数第二个顶点。
    • 如果第二种情况为真,删除第一个顶点,继续检查。
    • 当两种情况都不为真时,停止。
  •     找出一个必定会在凸包内的中点 计算每个点和中点的连线与x轴的夹角(在 0——360 度的范围内) 根据这些夹角对顶点排序 加入最初的两个顶点 对于除最后一个顶点以外的其余顶点 让其成为凸包上的下一个顶点 检查它和前面两个顶点组成的角是否大于 180 度 如果它和前面两个顶点组成的角大于 180 度,那么把它前面那个顶点删掉 加入最后一个顶点 完成上述的删除任务 检查最后一个顶点和它的前一个顶点和第一个顶点所组成的角是否大于 180 度,或者最后一个顶点和第一、第二个顶点组成的角是否大于 180 度。 如果第一种情况为真,删除最后一个顶点,并且检查倒数第二个顶点。 如果第二种情况为真,删除第一个顶点,继续检查。 当两种情况都不为真时,停止。

        但是我觉得:

  • 找出一个必定会在凸包内的中点
  • 计算每个点和中点的连线与x轴的夹角(在 0——360 度的范围内)
  • 根据这些夹角对顶点排序
  • 加入最初的两个顶点
  • 对于除最后一个顶点以外的其余顶点
  • 让其成为凸包上的下一个顶点
  • 检查它和前面两个顶点组成的角是否大于 180 度
    • 如果它和前面两个顶点组成的角大于 180 度,那么把它前面那个顶点删掉 ,继续判断
  • 加入最后一个顶点
    • 完成上述的删除任务
    • 检查最后一个顶点和它的前一个顶点和第一个顶点所组成的角是否大于 180 度,或者最后一个顶点和第一、第二个顶点组成的角是否大于 180 度。
    • 如果第一种情况为真,删除最后一个顶点,并且检查倒数第二个顶点。
    • 如果第二种情况为真,删除第一个顶点,继续检查。
    • 当两种情况都不为真时,停止。
  •    如果没有继续判断的话,是会出错的。代码如下:

    #include <iostream> #include <fstream> #include <math.h> #define SIZE 10000 using namespace std; typedef struct Point { double x; double y; } Vertex; ifstream fin("fc.in"); ofstream fout("fc.out"); int n; Point p[SIZE]; Point center; double angle[SIZE]; int rank[SIZE]; int path[SIZE] = {0}; int ps = 0; int pl = 0; //计算所有点的中点 void calCenter() { double x = 0.0; double y = 0.0; for (int i=0; i <n; i ++) { x += p[i].x; y += p[i].y; } center.x = x / n; center.y = y / n; } //用中点为原点,重新建立坐标系 void initPoint() { for (int i=0; i < n; i ++) { p[i].x -= center.x; p[i].y -= center.y; } } //计算点与x轴的夹角 double calAngle(int index) { double x = p[index].x; double y = p[index].y; double angle = y / sqrt((double)(x * x + y* y)); if (x >=0 && y>=0) { return angle; } else if (x < 0 && y >= 0) { return (2.0 - angle); } else if (x<= 0 && y<0) { return (2.0 - angle); } else if (x>=0 && y <0) { return (4.0 + angle); } } void sort() { for(int i=0; i < n; i ++) rank[i] = i; //冒泡排序 for (int i=0; i < n; i ++) { for (int j=i+1; j < n; j ++) { if (angle[i] > angle[j]) { double temp = angle[j]; angle[j] = angle[i]; angle[i] = temp; int index = rank[j]; rank[j] = rank[i]; rank[i] = index; } }//end for j }//end for i } bool isConvex(int index, int p1,int p2 ) { Vertex v1; v1.x = p[p2].x - p[p1].x; v1.y = p[p2].y - p[p1].y; Vertex v2; v2.x = p[index].x - p[p1].x; v2.y = p[index].y - p[p1].y; double cha = v2.x * v1.y - v2.y * v1.x; if (cha > 0) { return true; } else return false; } void getConvexLine() { //将两个节点加入列表 path[0] = rank[0]; path[1] = rank[1]; pl = 2; for(int i=2; i < n; i ++) { bool flag = true; while (flag) { if (isConvex(rank[i],path[pl-1], path[pl-2])) { path[pl] = rank[i]; pl ++; flag = false; } else { pl --; if (pl>=2) { flag =true; } else { path[pl] = rank[i]; pl ++; flag = false; } }//end else } }//end for bool flag= true; while(flag) { flag = false; if(!isConvex(path[ps], path[pl-1], path[pl-2])) { pl --; flag = true; }//end ps if(!isConvex(path[ps+1], path[ps], path[pl-1])) { ps ++; flag = true; }//end pl } } double GetResult() { double result = 0.00; double x1,y1,x2,y2; for (int i=ps; i < pl -1; i ++) { x1 = p[path[i]].x; y1 = p[path[i]].y; x2 = p[path[i+1]].x; y2 = p[path[i+1]].y; result += sqrt((y2-y1)*(y2-y1) + (x2-x1) * (x2-x1)); } x1 = p[path[ps]].x; y1 = p[path[ps]].y; x2 = p[path[pl-1]].x; y2 = p[path[pl-1]].y; result += sqrt((y2-y1)*(y2-y1) + (x2-x1) * (x2-x1)); return result; } int main() { //输入数据 fin >> n; for (int i=0; i < n; i ++) { fin >>p[i].x >> p[i].y; } //计算中心 calCenter(); //以中心为原点,重新确立坐标系 initPoint(); //计算角度 for(int i=0; i < n; i ++) { angle[i] = calAngle(i); } //排序 sort(); getConvexLine(); fout.setf(ios::fixed); fout.precision(2); fout<< GetResult() <<endl; return 0; }

        运行结果如下:

    Executing... Test 1: TEST OK [0.011 secs, 3328 KB] Test 2: TEST OK [0.011 secs, 3328 KB] Test 3: TEST OK [0.000 secs, 3328 KB] Test 4: TEST OK [0.011 secs, 3328 KB] Test 5: TEST OK [0.054 secs, 3328 KB] Test 6: TEST OK [0.086 secs, 3328 KB] Test 7: TEST OK [0.205 secs, 3328 KB] Test 8: TEST OK [0.680 secs, 3328 KB] All tests OK.

        后来我看了别人的70行代码,自叹不如

    #include <fstream> #include <cstdlib> #include <cstring> #include <cmath> using namespace std; ifstream fin("fc.in"); ofstream fout("fc.out"); struct node{double x,y;}; node p0,p[10000],a[10000],b[10000]; static int n,pn,pb; static double ans=0; double cross(node o,node a,node b) { return (a.x-o.x)*(b.y-o.y)-(b.x-o.x)*(a.y-o.y); } int cmp(const void * x,const void * y) { node a=*(node *)x,b=*(node *)y; return cross(p0,a,b)<0; } double dis(node a,node b) { return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)); } int main() { //-----------------------------------init fin>>n;p0.x=1000000000;p0.y=1000000000; for(int i=0;i<n;i++) { fin>>p[i].x>>p[i].y; if(p[i].x<p0.x){p0=p[i];pn=i;} else if(p[i].x==p0.x&&p[i].y<p0.y){p0=p[i];pn=i;} } //-----------------------------------极角排序 int j=-1; for(int i=0;i<n;i++) if(i!=pn){j++;a[j]=p[i];} qsort(a,n-1,sizeof(a[0]),cmp); //------------------------------------Graham Scan b[0]=p0;b[1]=a[0];pb=2; for(int i=1;i<n-1;i++) { b[pb]=a[i]; while(cross(b[pb-2],b[pb-1],b[pb])<=0) { pb--;b[pb]=a[i]; } pb++; } //--------------------------------------Calculate for(int i=1;i<pb;i++) ans+=dis(b[i-1],b[i]); ans+=dis(b[0],b[pb-1]); fout.setf(ios::fixed); fout.precision(2); fout<<ans<<endl; return 0; }

        速度也快了一个数量级, 这就是差距吗?

    Executing... Test 1: TEST OK [0.011 secs, 3480 KB] Test 2: TEST OK [0.000 secs, 3480 KB] Test 3: TEST OK [0.000 secs, 3480 KB] Test 4: TEST OK [0.032 secs, 3480 KB] Test 5: TEST OK [0.011 secs, 3480 KB] Test 6: TEST OK [0.032 secs, 3480 KB] Test 7: TEST OK [0.043 secs, 3480 KB] Test 8: TEST OK [0.065 secs, 3480 KB] All tests OK.

     

    你可能感兴趣的:(USACO算法系列四十一——fc)