2013資料結構課程設計之便利店選址(暴力列舉或隨機函式或三分)

果7發表於2013-09-12

[問題描述]

某小區決定在小區內部建一家便利店,現小區內部共有八棟樓,它們的地理座標分別為:(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
*/


然後是gcz在我們班群裡面說的三分的方法,這個三分很久都沒寫過了,這個也是二維的三分。二分是解決單調性不變的函式,而三分則是解決類似拋物線這樣的函式,有一個凸點或凹點。後來問了一下同學,思路是這樣,先三分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,找到最小的.感謝小雨,gcz
        {
            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
*/


相關文章