最优配餐 广度优先搜索

问题描述
  栋栋最近开了一家餐饮连锁店,提供外卖服务。随着连锁店越来越多,怎么合理的给客户送餐成为了一个急需解决的问题。
  栋栋的连锁店所在的区域可以看成是一个n×n的方格图(如下图所示),方格的格点上的位置上可能包含栋栋的分店(绿色标注)或者客户(蓝色标注),有一些格点是不能经过的(红色标注)。
  方格图中的线表示可以行走的道路,相邻两个格点的距离为1。栋栋要送餐必须走可以行走的道路,而且不能经过红色标注的点。


  送餐的主要成本体现在路上所花的时间,每一份餐每走一个单位的距离需要花费1块钱。每个客户的需求都可以由栋栋的任意分店配送,每个分店没有配送总量的限制。
  现在你得到了栋栋的客户的需求,请问在最优的送餐方式下,送这些餐需要花费多大的成本。
输入格式
  输入的第一行包含四个整数n, m, k, d,分别表示方格图的大小、栋栋的分店数量、客户的数量,以及不能经过的点的数量。
  接下来m行,每行两个整数xi, yi,表示栋栋的一个分店在方格图中的横坐标和纵坐标。
  接下来k行,每行三个整数xi, yi, ci,分别表示每个客户在方格图中的横坐标、纵坐标和订餐的量。(注意,可能有多个客户在方格图中的同一个位置)
  接下来d行,每行两个整数,分别表示每个不能经过的点的横坐标和纵坐标。
输出格式
  输出一个整数,表示最优送餐方式下所需要花费的成本。
样例输入
10 2 3 3
1 1
8 8
1 5 1
2 3 3
6 7 2
1 2
2 2
6 8
样例输出
29
评测用例规模与约定
  前30%的评测用例满足:1<=n <=20。
  前60%的评测用例满足:1<=n<=100。
  所有评测用例都满足:1<=n<=1000,1<=m, k, d<=n^2。可能有多个客户在同一个格点上。每个客户的订餐量不超过1000,每个客户所需要的餐都能被送到。 


 问题分析——

思路一:
反过来想,则问题变成了订外卖的人去找最近的外卖店,利用广度优先搜索,找到离这个人最近的外卖店。
实现广度优先搜索用到了队,即先将一个点进队,并搜索其周围的四个点,如果符合相应的条件(既不是障碍也不是外卖店),就将这个点进队。
在下面的代码中,map表示地图,通过将vice_map[i][j]标记为1,代表map上的点[i][j]已经被访问。

#include<stdio.h>

#define N 1010

typedef struct node				//x,y记录进队的点的坐标
{
	int x, y;
	int value;					//value记录数值,用于判断进队的点是不是外卖店(value = -1)
	int depth;					//depth记录人到外卖店的距离
}Node;

int n, dep = 0, rear =-1, front = -1;
int map[N][N] = {0}, vice_map[N][N] = {0};
Node queue[N * N] = {0};

void search(int i, int j);

int main()
{
	int shop, guest, obs;							// obs: obstacle
	int	i, j, k, m, row, col;
	int	num_eat;
	long long sum = 0;

	// read the information
	scanf("%d%d%d%d", &n, &shop, &guest, &obs);

	for(i=0; i<shop; i++)
	{
		scanf("%d%d", &row, &col);
		map[row][col] = -1;							//外卖店用-1表示
	}

	for(i=0; i<guest; i++)
	{
		scanf("%d%d%d", &row, &col, &num_eat);
		if(map[row][col] != 0)
			map[row][col] += num_eat;			//某个点的值代表所叫外卖的份数(>1)
        else
            map[row][col] = num_eat;
	}

	for(i=0; i<obs; i++)
	{
		scanf("%d%d", &row, &col);	
		map[row][col] = -2;							//障碍用-2表示
	}

	
	for(i=1; i<=n; i++)
        for(j=1; j<=n; j++)
        {
            front = rear = -1;						//初始化
            dep = 0;


            if(map[i][j] > 0)						//发现一个订外卖的点
            {
                for(k=1; k<=n; k++)					//初始化vice_map
                    for(m=1; m<=n; m++)
                        vice_map[k][m] = 0;

                queue[++rear].x = i;				//将这个订外卖点的信息加入队列
                queue[rear].y = j;
                queue[rear].value = map[i][j];
                queue[rear].depth = dep;


                vice_map[i][j] = 1;					//并在vice_map相对位置上进行标记


//广度优先算法,对一个点退队,并将其周围的符合条件的点加入队列
                while(1)							//发现商店就停止搜索,
                {
                    dep = queue[front].depth + 1;
                    search(queue[front].x - 1, queue[front].y);
                    if(queue[rear].value == -1)
                        break;
                    search(queue[front].x, queue[front].y + 1);
                    if(queue[rear].value == -1)
                        break;
                    search(queue[front].x + 1, queue[front].y);
                    if(queue[rear].value == -1)
                        break;
                    search(queue[front].x, queue[front].y - 1);
                    if(queue[rear].value == -1)
                        break;
                }
                sum += map[i][j] * queue[front].depth;                            //计算所花的钱
            }
        }

	printf("%I64d", sum);
	return 0;
}

void search(int i, int j)	//对于一个点,如果没有超出边界,且既不是障碍也没有被搜索过,则加入队列
{
	if(i > 0 && i <= n && j> 0 && j <= n && map[i][j] != -2 && vice_map[i][j] != 1)
	{
		queue[++rear].x = i;
		queue[rear].y = j;
		queue[rear].value = map[i][j];
		queue[rear].depth = dep;
		vice_map[i][j] = 1;	//标记map[i][j]点为已被搜索过
	}
}

但是这个算法并不是很好,因为耗时太长——对于每一个人,都要在全图广度优先搜索一遍,如果人数比较多,地图有比较大,显然很耗时。(在平台提交之后,代码运行耗时1.1秒左右!)

所以请教大神们之后,有了下面的算法——
将全部的外卖店进队,并对全图进行广度优先搜索,将每个点到最近的外卖店的距离记录在vice_map的相应点中。这样的话只对全图进行了一次广度优先搜索,就可以知道每个点离最近的外卖店的距离,相对于之前的每个人对全图广度优先搜索一次,找到这个人到最近的外卖点的距离的算法快了不止一两点!
#include<stdio.h>

#define N 1010

typedef struct node
{
	int x, y;
	int depth;
}Node;

int n, dep = 0, rear =-1, front = -1;
int map[N][N] = {0}, vice_map[N][N] = {0};
Node queue[N * N] = {0};

void search_point(int i, int j, int dep);
void BFS();

int main()
{
	int shop, guest, obs;
	int	i, j, row, col;
	int	num_eat;
	long long sum = 0;

	//读数据
	scanf("%d%d%d%d", &n, &shop, &guest, &obs);

	for(i=0; i<shop; i++)
	{
		scanf("%d%d", &row, &col);
		map[row][col] = -1;
	}

	for(i=0; i<guest; i++)
	{
		scanf("%d%d%d", &row, &col, &num_eat);
		if(map[row][col] != 0)
			map[row][col] += num_eat;
        else
            map[row][col] = num_eat;
	}

	for(i=0; i<obs; i++)
	{
		scanf("%d%d", &row, &col);
		map[row][col] = -2;
	}

	//将所有的外卖店进队
	for(i=1; i<=n; i++)
		for(j=1; j<=n; j++)
		{
			if(map[i][j] == -1)
            {
                queue[++rear].x = i;
                queue[rear].y = j;
                queue[rear].depth = 0;
                vice_map[i][j] = 1;
            }
		}

		BFS();

		for(i=1; i<=n; i++)
            for(j=1; j<=n; j++)
                if(map[i][j] > 0)
                    sum += (long long)map[i][j] * vice_map[i][j];

	printf("%I64d\n", sum);
	return 0;
}

void BFS()
{
    while(++front < rear)		//将外卖店出对,并向外扩散,对全图的点进行搜索
    {
        search_point(queue[front].x - 1, queue[front].y, queue[front].depth);
        search_point(queue[front].x, queue[front].y + 1, queue[front].depth);
        search_point(queue[front].x + 1, queue[front].y, queue[front].depth);
        search_point(queue[front].x, queue[front].y - 1, queue[front].depth);
    }
}

void search_point(int i, int j, int dep)
{
	if(i > 0 && i <= n && j> 0 && j <= n && map[i][j] != -2 && vice_map[i][j] == 0)
	{
		queue[++rear].x = i;
		queue[rear].y = j;
		queue[rear].depth = dep + 1;
		vice_map[i][j] = queue[rear].depth;		//vice_map[][]记录的是某个点到最近的商店的距离
	}
}

这个算法才用时160ms左右,显然比上一个代码快了不止一两点!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值