【仓库设置问题】

问题:

某公司在高速公路一些服务站内开设了百货超市,为了能及时给这些百货超市提供足够的商品,他们需要在一些百货超市旁修建仓库。一个仓库可以同时为多家百货超市提供服务,以满足各个超市对商品的需求。现已知这些百货超市在高速公路上的位置以及需要修建的仓库的数量。请编写程序确定每个仓库修建的位置以及所服务的超市,使所有仓库与所服务的百货超市的距离的总和最小,程序输出所求得的总的最小距离和。

要求仓库必须修在有超市的服务站内不同的仓库必须修在不同的位置,不能修在同一服务站内,在同一服务站内的超市和仓库之间的距离可忽略不计

输入描述:

输入的第一行有两个整数n和k,分别表示超市和仓库的数量,其中1 <= n <= 200, 1 <= k <= 30, k <= n。其后的n行,每一行有一个整数,表示每个超市的位置(相对于高速公路起点的距离)。

输出描述:

输出1行,一个整数,表示所有仓库与所服务的超市的距离的总和。

样例输入:

6 3

5

6

12

19

20

27

样例输出:

8

思路:

采用回溯法

解空间树是子集树。

我们可以在递归分支被目标函数截断后计算最小距离,如果距离小于最佳距离,更新。

算法思想:

回溯法。目标函数min(sum(dis))各个商店与某一仓库的距离的和的最小值。约束函数(cnt==k)仓库数目为k不重不漏,if(sign[i]) continue;不可重复设置。

一个是针对选出设置仓库的组合,一个是针对求出最小距离。

对于前者,我用01树,分别考虑选和不选,直到选取数量cnt为k,进行后者的考虑。如果已经遍历了所有的商店,此时i > n,且cnt != k,则该选取路线不合理舍去。

对于后者,我采用双指针,在预先进行升序排序的shops数组上,从商店编号开始,分别向左和向右找到最近设置的仓库,如果只有一边有,则最短距离就相关于这个仓库;否则,求出两个仓库的距离的一半dis与考察的仓库的距离进行比较,若dis较小,则该仓库距离左指针所指示的仓库更近,反之,为右指针。最后按照这个方法考察每个仓库,求和即可。

代码(一个类似01树的方法):

#include<bits/stdc++.h>
using namespace std;

int shops, wares;
int result = INT_MAX;

int cal(int *shop, int *sign)
{
    int temp = 0;

    for(int i = 1; i <= shops; i++)
    {
        if(!sign[i])
        {
            int a = i;
            int b = i;
            while(!sign[a] && a != 0) a--;
            while(!sign[b] && b != shops+1) b++;

            if(a == 0) temp += shop[b] - shop[i];
            else if(b == shops+1) temp += shop[i] - shop[a];
            else if((float)i < float(a+b)/2) temp += shop[i] - shop[a];
            else temp += shop[b] - shop[i];
        }
    }
    return temp;
}
void dfs(int *shop, int *sign, int cnt)
{
    if(cnt > wares)
    {
        int dis = cal(shop, sign);
        if(dis < result) result = dis;
        return;
    }

    for(int i = 1; i <= shops; i++)
    {
        if(sign[i]) continue;
        sign[i] = 1;
        dfs(shop, sign, cnt+1);
        sign[i] = 0;
    }
}
int main()
{
    cin >> shops >> wares;

    int *shop = new int[shops+2]();
    int *sign = new int[shops+2]();
    for(int i = 1; i <= shops; i++)
    {
        cin >> shop[i];
    }
    sort(shop+1, shop+shops+1);

    dfs(shop, sign, 1);

    delete [] shop;
    delete [] sign;

    cout << result << endl;

    return 0;
}

代码(01树):

#include<bits/stdc++.h>
using namespace std;
int n, k;
int shops[10010];
int sign[10010];

int cal()
{
    int temp = 0;

    for(int i = 1; i <= n; i++)
    {
        if(!sign[i])
        {
            int a = i;
            int b = i;
            while(!sign[a] && a != 0) a--;
            while(!sign[b] && b != n+1) b++;

            if(a == 0) temp += shops[b] - shops[i];
            else if(b == n+1) temp += shops[i] - shops[a];
            else if((float)i < float(a+b)/2) temp += shops[i] - shops[a];
            else temp += shops[b] - shops[i];
        }
    }
    return temp;
}
int dfs(int i, int left)
{
    int result = INT_MAX;

    if(left == 0) return cal();
    if(i > n) return INT_MAX;

    int temp;

    sign[i] = 1;
    temp = dfs(i+1, left-1);
    result = min(result, temp);

    sign[i] = 0;
    temp = dfs(i+1, left);
    result = min(result, temp);

    return result;
}
int main()
{
    cin >> n >> k;

    for(int i = 1; i <= n; i++)
    {
        cin >> shops[i];
    }

    sort(shops+1, shops+1+n);

    cout << dfs(1, k) << endl;

    return 0;
}

  • 9
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值