[问题描述]
某小区决定在小区内部建一家便利店,现小区内部共有八栋楼,它们的地理坐标分别为:(10,20) (30,34) (19,25) (38,49.1) (9,38.1) (2,34) (5,8) (29,48)。同时,其中的住户人数分别为:30, 45, 28, 8, 36, 16, 78, 56。为了方便更多的住户购物,要求实现总体最优,请问便利店应该建立在哪里?
【提示】
1)便利店无论选址何处,八栋楼的居民均可直接到达,即八栋楼与便利店均相邻,且距离为直线距离;
2)八栋楼的居民人数为权重,应该方便大多数人,实现总体最优。
先是暴力找点的方法。
解题思路:自己开始想的就是暴力枚举,先找大范围,再找小范围。做这个题目就想到了的warmup2的1002题,但当时就是A不了。思路很简单,一步步地精确范围。先把整个地方划分成10*10的方格,再在里面找哪个最小,然后继续10*10每次都这样划分,精度确定跳出循环即可。详解见代码。
代码:
#include<iostream> #include<cstring> #include<string> #include<cmath> #include<cstdio> #include<algorithm> #include<ctime> using namespace std; int n; //n栋楼 double minx,miny,maxx,maxy,px,py; //找到四个边界 double ansx,ansy,sum; //最后的结果 struct mq { double x; double y; int peo; }; mq node[1005]; double cal(double px,double py) //计算值 { int i; double sum=0; for(i=0;i<n;i++) sum+=sqrt((px-node[i].x)*(px-node[i].x)+(py-node[i].y)*(py-node[i].y))*node[i].peo; return sum; } void solve() { double i,j; double fenx=maxx-minx; //把x分成若干份 double feny=maxy-miny; //把y分成若干份 while(fenx>0.01&&feny>0.01) //暴力找点 { fenx/=10.0,feny/=10.0; for(i=minx;i<=maxx;i+=fenx) for(j=miny;j<=maxy;j+=feny) { double tmp=cal(i,j); if(tmp<sum) { sum=tmp; ansx=i; ansy=j; } } minx=ansx-fenx; miny=ansy-feny; maxx=ansx+fenx; maxy=ansy+feny; } } int main() { int i; //freopen("input.txt","r",stdin); //freopen("output.txt","w",stdout); while(~scanf("%d",&n)) //n栋楼 { scanf("%lf%lf%d",&node[0].x,&node[0].y,&node[0].peo); minx=maxx=node[0].x; miny=maxy=node[0].y; //找到四个边界 for(i=1;i<n;i++) { scanf("%lf%lf%d",&node[i].x,&node[i].y,&node[i].peo); if(node[i].x<minx) minx=node[i].x; if(node[i].x>maxx) maxx=node[i].x; if(node[i].y<miny) miny=node[i].y; if(node[i].y>maxy) maxy=node[i].y; } sum=100000000; solve(); cout<<"便利店选址坐标为:"<<endl; cout<<"x: "<<ansx<<" "<<"y: "<<ansy<<endl; cout<<"最优解为: "<<sum<<endl; } return 0; } /* 8 10 20 30 30 34 45 19 25 28 38 49.1 8 9 38.1 36 2 34 16 5 8 78 29 48 56 便利店选址坐标为: x: 16.5404 y: 27.4362 最优解为: 5146.85 */
然后就是LCM说的随机算法,也没想到那一块,被他一说是有些道理,但是点的范围一扩大的话,就没那么准确了。
随机算法,我是直接采用的随机数然后%lenth,区间宽度,相当于0~lenth-1。随机函数对范围要求比较严格吧,范围如果太大,随机也没用了。具体实现见代码:
代码:
#include<iostream> #include<cstring> #include<string> #include<cmath> #include<cstdio> #include<algorithm> #include<ctime> using namespace std; int n; //n栋楼 struct mq { double x; double y; int peo; }; mq node[1005]; double cal(double px,double py) //计算值 { int i; double sum=0; for(i=0;i<n;i++) sum+=sqrt((px-node[i].x)*(px-node[i].x)+(py-node[i].y)*(py-node[i].y))*node[i].peo; return sum; } int main() { int i; freopen("input.txt","r",stdin); freopen("output.txt","w",stdout); double minx,miny,maxx,maxy,px,py; //找到四个边界 double ansx,ansy; //最后的结果 while(~scanf("%d",&n)) //n栋楼 { scanf("%lf%lf%d",&node[0].x,&node[0].y,&node[0].peo); minx=maxx=node[0].x; miny=maxy=node[0].y; //找到四个边界 for(i=1;i<n;i++) { scanf("%lf%lf%d",&node[i].x,&node[i].y,&node[i].peo); if(node[i].x<minx) minx=node[i].x; if(node[i].x>maxx) maxx=node[i].x; if(node[i].y<miny) miny=node[i].y; if(node[i].y>maxy) maxy=node[i].y; } minx*=100,maxx*=100,miny*=100,maxy*=100; //边界扩大一百倍 //找到边界了就可以随机了 int lenx,leny; lenx=ceil(maxx-minx),leny=ceil(maxy-miny); double sum=1000000000; srand((unsigned)time(NULL)); //播种 for(i=1;i<=500000;i++) //随机50W次 { px=rand()%lenx+minx; py=rand()%leny+miny; px/=100.0; py/=100.0; double tmp=cal(px,py); if(tmp<sum) { sum=tmp; ansx=px; ansy=py; } } cout<<"便利店选址坐标为:"<<endl; cout<<"x: "<<ansx<<" "<<"y: "<<ansy<<endl; cout<<"最优解为: "<<sum<<endl; } return 0; } /* 8 10 20 30 30 34 45 19 25 28 38 49.1 8 9 38.1 36 2 34 16 5 8 78 29 48 56 便利店选址坐标为: x: 16.56 y: 27.44 最优解为: 5146.85 */
然后是郭承珍在我们班群里面说的三分的方法,这个三分很久都没写过了,这个也是二维的三分。二分是解决单调性不变的函数,而三分则是解决类似抛物线这样的函数,有一个凸点或凹点。后来问了一下同学,思路是这样,先三分x,在三分x的基础上再三分y在y里面找一个满足条件的最小的一个,大题思路是这样,具体实现见代码。
代码:
#include<iostream> #include<cstring> #include<string> #include<cmath> #include<cstdio> #include<algorithm> #include<ctime> using namespace std; int n; //n栋楼 double minx,miny,maxx,maxy; //找到四个边界 double ansx,ansy,sum; //最后的结果 double midy; //便于最后输出 double eps=0.001; struct mq { double x; double y; int peo; }; mq node[1005]; double dis(double px,double py) //算sum值 { int i; double sum=0; for(i=0;i<n;i++) sum+=sqrt((px-node[i].x)*(px-node[i].x)+(py-node[i].y)*(py-node[i].y))*node[i].peo; return sum; } double cal(double x) //每次返回x确定三分y使得dis(x,y)最小的x,y. { double lefty,righty,mimidy; lefty=miny,righty=maxy; while(righty-lefty>eps) { midy=(lefty+righty)/2.0,mimidy=(righty+midy)/2.0; if(dis(x,midy)<dis(x,mimidy)) righty=mimidy; else lefty=midy; } return dis(x,midy); } int main() { int i; //freopen("input.txt","r",stdin); //freopen("output.txt","w",stdout); while(~scanf("%d",&n)) //n栋楼 { scanf("%lf%lf%d",&node[0].x,&node[0].y,&node[0].peo); minx=maxx=node[0].x; miny=maxy=node[0].y; for(i=1;i<n;i++) //找到四个边界 { scanf("%lf%lf%d",&node[i].x,&node[i].y,&node[i].peo); if(node[i].x<minx) minx=node[i].x; if(node[i].x>maxx) maxx=node[i].x; if(node[i].y<miny) miny=node[i].y; if(node[i].y>maxy) maxy=node[i].y; } double leftx,rightx,midx,mimidx; leftx=minx,rightx=maxx; while(rightx-leftx>eps) //三分x的基础上三分y,找到最小的.感谢小雨,郭承珍 { midx=(leftx+rightx)/2.0,mimidx=(rightx+midx)/2.0; if(cal(midx)<cal(mimidx)) rightx=mimidx; else leftx=midx; } ansx=midx,ansy=midy; sum=dis(ansx,ansy); cout<<"便利店选址坐标为:"<<endl; cout<<"x: "<<ansx<<" "<<"y: "<<ansy<<endl; cout<<"最优解为: "<<sum<<endl; } return 0; } /* 8 10 20 30 30 34 45 19 25 28 38 49.1 8 9 38.1 36 2 34 16 5 8 78 29 48 56 便利店选址坐标为: x: 16.5389 y: 27.4345 最优解为: 5146.85 */