首页 > 试题广场 >

安排超市

[编程题]安排超市
  • 热度指数:857 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 256M,其他语言512M
  • 算法知识视频讲解
给定一个n*n的地图。地图是上下左右四联通的,不能斜向行走:
*代表障碍,不可通行。
.代表路,可以通行。
#代表房子。房子也是可以通行的。

小红现在需要在一些地方安排一些超市(不能安排在障碍物上,可以安排在路上或者房子上。超市也是可以通行的)。
小红希望每个房子至少可以到达一个超市。同时由于成本原因,小红希望超市的数量尽可能少。
在超市数量最少的情况下,小红希望每个房子到达最近的超市的距离之和尽可能小。
她想知道超市最少的数量,以及最小的距离之和。你能帮帮她吗?

输入描述:
第一行一个正整数n,代表地图的大小。( 1<=n<=50 )
接下来的n行,每行一个长度为n的字符串,表示整个地图。保证输入合法。


输出描述:
输出两个整数,用空格隔开。分别代表超市的最小数量、最小的距离之和。
示例1

输入

3
#.#
.**
*.#

输出

2 2

说明

下标从1开始,第一个超市安排的位置是(1,2),第二个超市安排的位置是(3,3)。三个房子到超市的距离分别为1,1,0。 
示例2

输入

3
#*#
.**
*.#

输出

3 0

说明

分别在三个房子上建3个超市即可。 
示例3

输入

2
.*
*.

输出

0 0

说明

没有房子,所以不用造超市 

分析

首先需要计算商店最少个数,每一个房子都可以前往商店购物,即'*'将地图分成了n份,那么就是n个商店(深度优先算法)
然后计算每个区域内的房子到达商店的距离和最小,也就保证了总体最小(广度优先算法)。

代码

import java.util.*;
/**
 * @author xuan
 * @date 2022/4/15 12:47
 */
public class No4 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        sc.nextLine();
        char[][] map = new char[n][n];
        char[][] map1 = new char[n][n];
        int[][] H = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
        int ans1 = 0;
        int ans2 = 0;
        for (int i = 0; i < n; i++) {
            map[i] = sc.nextLine().trim().toCharArray();
            map1[i] = map[i].clone();
        }
        //对每个节点进行深度优先递归统计区域个数,并且统计区域内包含的节点
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                //节点为房子需要递归。可能一个区域内没有'#',那么不需要递归
                if (map1[i][j] == '#') {
                    ans1++; //区域个数也就是商店数量
                    List list = new ArrayList();//统计本区域内的所有节点
                    dfs(map1, i, j, list);//深度优先递归将这个区域内的节点全部遍历
                    int min = Integer.MAX_VALUE;
                    //将商店设置在coordinate,统计此区域内最小距离和
                    for (int[] coordinate : list) {
                        int min1 = 0;
                        int step = 0; //商店到节点的距离
                        //广度优先
                        boolean[][] flag = new boolean[n][n];
                        Queue queue = new LinkedList();
                        queue.offer(coordinate);
                        flag[coordinate[0]][coordinate[1]] = true;
                        while (!queue.isEmpty()) {
                            int size = queue.size();
                            for (int k = 0; k < size; k++) {
                                int[] poll = queue.poll();
                                int x = poll[0], y = poll[1];
                                //如果此节点是房子,那么需要统计距离
                                if (map[x][y] == '#') {
                                    min1 += step;
                                }
                                for (int l = 0; l < 4; l++) {
                                    int xx = x + H[l][0];
                                    int yy = y + H[l][1];
                                    //超过边界或者不是此区域或者遍历过 则不加入队列
                                    if (xx = n || yy >= n || map[xx][yy] == '*' || flag[xx][yy]) {
                                        continue;
                                    }
                                    queue.offer(new int[]{xx, yy});
                                    //标记已遍历
                                    flag[xx][yy] = true;
                                }
                            }
                            step++;
                        }
                        //求出此区域内的最小距离和
                        min = Math.min(min1, min);
                    }
                    //总的距离和
                    ans2 += min;
                }
            }
        }
        System.out.println(ans1 + " " + ans2);
    }
    /**
     * 
     * @param map 备份的地图
     * @param x 纵坐标
     * @param y 横坐标
     * @param list 统计此区域内的节点
     */
    public static void dfs(char[][] map, int x, int y, List list) {
        int n = map.length;
        if (x = n || y >= n || map[x][y] == '*') {
            return;
        }
        list.add(new int[]{x, y});
        //标记已遍历
        map[x][y] = '*';
        //遍历上下左右
        dfs(map, x - 1, y, list);
        dfs(map, x + 1, y, list);
        dfs(map, x, y - 1, list);
        dfs(map, x, y + 1, list);
    }
}
编辑于 2022-04-15 16:32:02 回复(0)
给提供一个思路,最后一个算例差一点点时间,感觉是找最短路那里有点写复杂了。
spread:深度优先把有房子所在的一整块找出来并标记房子为1,路为0
caldis:广度优先找一块中每个点出发的最短路,利用一个和地图一样大的二维矩阵记录是否访问过
best_dis:遍历块中每个位置,找到整个块中的最短路径成本
poss每次记录一整块的可用位置,houses记录一整块的房子

n = int(input())
matrix = []
for _ in range(n):
    line = (' '.join(input())).split()
    matrix.append(line)

def spread(x, y,):
    if x<0&nbs***bsp;x>=n&nbs***bsp;y<0&nbs***bsp;y>=n:
        return
    if matrix[x][y] == '.':
        matrix[x][y] = '0' #路
        poss.append([x,y])
    elif matrix[x][y] == '#':
        matrix[x][y] = '1' #房
        houses.append([x,y])
        poss.append([x,y])
    else:
        return
    spread(x+1,y)
    spread(x,y+1)
    spread(x-1,y)
    spread(x,y-1)

def caldis(x,y):
    totaldis = 0
    cur = [[x,y]]
    rec = [[False]*n for _ in range(n)]
    depth = 0
    while cur:
        temp = []
        for pos in cur:
            if pos[0] < 0&nbs***bsp;pos[0] >= n&nbs***bsp;pos[1] < 0&nbs***bsp;pos[1] >= n:
                continue
            if rec[pos[0]][pos[1]]&nbs***bsp;matrix[pos[0]][pos[1]] == '*': #障碍或者遍历过了
                continue
            if not rec[pos[0]][pos[1]]: # 没遍历过
                if matrix[pos[0]][pos[1]] == '1': #且是房子
                    totaldis += depth
                '''访问并扩散'''
                rec[pos[0]][pos[1]] = True
                temp.append([pos[0] + 1,pos[1]])
                temp.append([pos[0] - 1, pos[1]])
                temp.append([pos[0], pos[1] + 1])
                temp.append([pos[0], pos[1] - 1])
        cur = temp
        depth += 1
    return totaldis

def best_dis():
    shortest_dis = float('inf')
    if len(houses) == 1:
        return 0
    for pos in poss:
        shortest_dis = min(shortest_dis, caldis(pos[0], pos[1]))
    return shortest_dis

count = 0
dis = 0
for i in range(n):
    for j in range(n):
        if matrix[i][j] == '#':
            poss = []
            houses = []
            count += 1
            spread(i,j)
            dis += best_dis()
else:
    print(count, dis)



发表于 2022-10-19 10:30:09 回复(0)
1.先求出有多少个不连通的区域,并记录该区域房子的数量,如果某个不连通区域是没有房子的,那就不需要建超市,如果有房子就要建一间超市。
2.在寻找不连通区域时,可以顺便记录每个区域的有哪些点,为之后算最短距离做准备。
3.暴力计算最短距离,对该区域的每个位置算一下该位置到该区域每间房子的距离;如果某个区域的房子的数量是1,那么该超市一定是建在该房子上,距离和是0。
#include<bits/stdc++.h>
using namespace std;
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};
struct Node{
    int x;
    int y;
    int dis;
};
//计算x,y到各个房子的距离之和
int dis(int x, int y, int N, vector<string> maps,
        vector<vector<int>> visited){
    queue<Node> q;
    q.push(Node{x,y,0});
    visited[x][y] = 1;
    int ret = 0;
    while(!q.empty()){
        int size = q.size();
        while (size--){
            Node cur = q.front();
            q.pop();
            if (maps[cur.x][cur.y] == '#') ret += cur.dis;
            for (int i = 0; i < 4; i++){
                int xx = cur.x + dx[i];
                int yy = cur.y + dy[i];
                int ddis = cur.dis + 1;
                if (xx >=0 && xx < N && yy >= 0 && yy < N && !visited[xx][yy]){
                    visited[xx][yy] = 1;
                    q.push(Node{xx,yy,ddis});
                }
            }
        }
    }
    return ret;
}
//搜索当前区域
int findArea(int x, int y, int N, vector<vector<int>> &visited,
              vector<pair<int,int>> &curArea, 
              vector<string> maps){
    if (x < 0 || x >= N || y < 0 || y >= N || visited[x][y]) return 0;
    visited[x][y] = 1;
    int ret = 0;
    //记录当前区域有多少房子
    if (maps[x][y] == '#') ret++;
    //记录当前区域所有点,为之后计算该区域所有位置到所有房子的距离做准备
    curArea.emplace_back(make_pair(x,y));
    //到处走走
    for(int i = 0; i < 4; i++){
        ret += findArea(x+dx[i], y+dy[i], N, visited,
                 curArea, maps);
    }
    return ret;
}
//计算当前区域在哪个位置建超市的总距离最短
int minDis(int N, vector<string> maps,
           vector<pair<int,int>> area,
           vector<vector<int>> visited){
    int ret = int(1e9);
    //该区域每个点都要试试
    for(auto& pos: area){
        ret = min(ret, dis(pos.first, pos.second, N, maps, visited));
    }
    return ret;
} 
int main(void){
    int N;
    cin >> N;
    vector<string> maps;
    vector<vector<int>> visited(N,vector<int>(N,0));
    //每个区域的所有点
    vector<vector<pair<int,int>>> area;
    //当前区域的所有点
    vector<pair<int,int>> curArea;
    //每个区域房子的数量
    vector<int> areaHouseNum;
    //当前区域房子的数量
    int curAreaHouseNum = 0;
    string temp;
    bool house = false;
    for (int i = 0; i < N; i++){
        cin >> temp;
        maps.emplace_back(temp);
        for(int j = 0; j < N; j++){
            if (temp[j] == '*') {
                visited[i][j] = 1;
            }
            else if(temp[j] == '#') house = true;
        }
    }
    //要没房子就别盖超市了
    if (!house){
        cout << 0 << ' ' << 0 << endl;
        return 0;
    }
    vector<vector<int>> copy(visited);
    for (int i = 0; i < N; i++){
        for (int j = 0; j < N; j++){
            if(!copy[i][j]){
                curArea.clear();
                curAreaHouseNum = findArea(i, j, N, copy, curArea, maps);
                if (curAreaHouseNum){
                    area.emplace_back(curArea);
                    areaHouseNum.emplace_back(curAreaHouseNum);
                }
                
            }
        }
    }
    int ans = 0;
    //对每个区域进行距离的统计
    for (int i = 0; i < area.size(); i++){
        //如果当前区域房子只有一间,那么超市肯定建在房子的地方
        if (areaHouseNum[i] == 1) continue;
        //否则就要计算当前区域在哪建超市的总距离最短
        ans += minDis(N, maps, area[i], visited);
    }
    cout << area.size() << ' ' << ans;
}


发表于 2022-08-30 00:06:57 回复(0)
随便暴力。
#include "bits/stdc++.h"
using namespace std;

int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);

	int n; cin >> n;
	vector<string> s(n);
	for(int i = 0;i < n; i++) cin >> s[i];

	static vector<int> dx{1, 0, -1, 0};
	static vector<int> dy{0, 1, 0, -1};

	vector<vector<bool>> vis(n, vector<bool>(n));	
	vector<pair<int, int>> p;

	bool flag = 0;
	auto bfs1 = [&](int x, int y) {
		queue<pair<int, int>> q;
		q.push(make_pair(x, y));
		while(!q.empty()) {
			int x, y;
			tie(x, y) = q.front();
			q.pop();
			if(s[x][y] == '#') flag = 1;
			p.push_back(make_pair(x, y));
			for(int i = 0;i < 4; i++) {
				int xx = x + dx[i];
				int yy = y + dy[i];
				if(xx < 0 || xx >= n || yy < 0 || yy >= n || s[xx][yy] == '*' || vis[xx][yy]) continue ;
				vis[xx][yy] = 1;
				q.push(make_pair(xx, yy));
			}
		}
	};

	auto bfs2 = [&]() -> int {
		int sum = 1E9;
		for(int i = 0;i < (int) p.size(); i++) {
			vector<vector<int>> dp(n, vector<int>(n, 1E9));
			dp[p[i].first][p[i].second] = 0;

			auto bfs = [&](int x, int y) {
				queue<tuple<int, int, int>> q;
				q.push(make_tuple(x, y, 0));
				while(!q.empty()) {
					int x, y, h;
					tie(x, y, h) = q.front();
					q.pop();
					for(int j = 0;j < 4; j++) {
						int xx = x + dx[j];
						int yy = y + dy[j];
						if(xx < 0 || xx >= n || yy < 0 || yy >= n || s[xx][yy] == '*' || dp[xx][yy] != 1E9) continue ;
						dp[xx][yy] = min(dp[xx][yy], h + 1);
						q.push(make_tuple(xx, yy, h + 1));
					}
				}
			};

			bfs(p[i].first, p[i].second);
			int temp = 0;
			for(int j = 0;j < (int) p.size(); j++) {
				if(s[p[j].first][p[j].second] == '#') {
					temp += dp[p[j].first][p[j].second];
				}
			}
			sum = min(sum, temp);
		}

		return sum;
	};

	int ans = 0, cnt = 0;
	for(int i = 0;i < n; i++) {
		for(int j = 0;j < n; j++) {
			if(s[i][j] == '*' || vis[i][j]) continue ;
			p.clear();
			flag = 0;
			vis[i][j] = 1;
			bfs1(i, j);
			if(!flag) continue ;
			cnt++;
			ans += bfs2();
		}
	}

	cout << cnt << " " << ans << endl;
}


发表于 2022-04-24 14:57:46 回复(0)

思路:先求区域,再在区域基础上求距离。
用例都过了,提交过1/5,烦请各位大佬看看有哪里搞错了,万分感谢!

#include 
#include 
#include 
#include 
using namespace std;
// 思路:首先求可连通区域,然后再在联通区域内求距离的最小值。
// BFS求联通区域内的房子和道路,cmap中存房子,cmap1中存路。
void BFS(vector& mm, int i , int j, vector>& cmap, vector>& cmap1){
    if (i = mm.size() || j = mm.size()) return;
    if (mm[i][j] == '*') return;
    if (mm[i][j] == '#') {
        cmap.push_back({i, j}); // 存房子
    }
    else if (mm[i][j] == '.'){
        cmap1.push_back({i, j}); // 存路
    }
    mm[i][j] = '*';
    BFS(mm, i + 1, j, cmap, cmap1);
    BFS(mm, i - 1, j, cmap, cmap1);
    BFS(mm, i, j + 1, cmap, cmap1);
    BFS(mm, i, j - 1, cmap, cmap1);
}
// 超市可以建在路和房子上,所以cmap和cmap1都是超市位置的候选,但只要求房子到超市的距离最小,内层循环一定遍历房子(cmap)
int disCalc(vector>& cmap, vector>& cmap1){
    int minD = INT32_MAX;
    for (int i = 0; i < cmap.size(); i++){
        int dis = 0;
        for (int j = 0; j < cmap.size(); j++){
            dis += abs(cmap[i][0] - cmap[j][0]) + abs(cmap[i][1] - cmap[j][1]); // 求距离
        }
        if (dis < minD){
            minD = dis;
        }
    }
    for (int i = 0; i < cmap1.size(); i++){
        int dis = 0;
        for (int j = 0; j < cmap.size(); j++){
            dis += abs(cmap1[i][0] - cmap[j][0]) + abs(cmap1[i][1] - cmap[j][1]);
        }
        if (dis < minD){
            minD = dis;
        }
    }
    return minD;
}
void deal(int n, vector& mat){
    vector mm(mat.begin(), mat.end());
    int cnt = 0;
    int disSum = 0;
    for (int i = 0; i < n; i++){
        for (int j = 0; j < n; j++){
            if (mm[i][j] != '*'){
                vector> cmap;
                vector> cmap1;
                BFS(mm, i, j, cmap, cmap1);
                if (cmap.size() == 0){ // 如果此联通区域内无房子,自然不用再计算距离和个数
                    continue;
                }
                else{
                    int dis = disCalc(cmap, cmap1);
                    disSum += dis;
                    cnt ++;
                }
            }
        }
    }
    cout << cnt << " " << disSum << endl;
}
int main(){
    int n;
    cin >> n;
    vector mat(n);
    for (int i = 0; i < n; i++){
        cin >> mat[i];
    }
    deal(n, mat);
    return 0;
}
发表于 2022-08-26 12:16:35 回复(1)
说下思路:
输出两个值:1:最少超市数量;2:每个房子到达最近的超市的最小距离之和
隐藏信息:在最少超市数量的前提下,选择超市的位置使得每个房子到达最近的超市的距离之和最小。
所以满足前提的基础上,再去计算每个房子到达最近的超市的最小距离之和。
那么如何满足最少超市数量呢?这个简单,只要找出被障碍物隔离的通行区域数量,那么每个通行区域内只要布置一个超市,就能满足这块区域内的所有的房子都能到达超市。
比如例图地图:
3
#*#             [1,1][1,2][1,3]
.**  对应的坐标   [2,1][2,2][2,3]
*.#             [3,1][3,2][3,3]
可分成三个被隔离的通行区域
1:[1,1]、[2,1]
2:  [1,3]
3:  [3,3]
即所需要的超市最小数量是3。

接下来计算每个房子到达最近的超市的距离之和最小。
简单点,直接穷举法(算法可优化):
计算出区域内每一个可通行位置,到达所在区域内所有房子的路径之和,取所有情况的最小值,也就是在这个区域内每个房子到达最近的超市的最小距离之和
把所有的被隔离区域的最小距离之和再加起来,就是输出的第二个值。

备注:思路应该是没错的,测试用例全部通过,但是提交运行没有完全通过(1/5)。常年使用C#,C/C++代码好久没写了,代码写的有点糟,可阅读性有点差。我还是写下注释吧,等哪个有缘人帮忙看下代码,帮我找找bug,万分感谢!

#include<iostream>
#include<vector>
#include<deque>

using namespace std;

// 区域大小
int N;

// 区域地图
char ts[50][50];

// 区域是否被访问过
// 用于第一步找出所有被隔离的区域
// 初始化设置false,被访问过设置true
bool visit[50][50];

// 区域id
// 相同的区域id,表示他们是在同一块区域
// 例如[1,1] = 1,[1,2] = 1,表示[1,1][1,2]在同一块区域内
// 初始化 -1
int field[50][50];

// 设置区域id使用,改成结构体会不会好理解点?
class tile
{
 public:
    char ch;  // '#' '.' '*'
    int i;    // 坐标
    int j;    // 坐标
    
    tile(int _i, int _j, char _ch)
    {
        i = _i;
        j = _j;
        ch = _ch;
        visit[j][i] = true;
    }
};

// 用于穷举法,计算区域内的可通行位置到达每个房子的距离之和
class result
{
public:
    bool hasSuper;   // 是否有超市(有些被隔离区域内可能没有超市)
    int minPath;     // 区域内每个房子到达超市最小距离之和
    result(bool h, int m)
    {
        hasSuper = h;
        minPath = m;
    }
};

// 题目说的要检查传参
bool isValidChar(char ch)
{
    // # 房子
    // . 道路
    // * 障碍
    return ch == '#' ||
        ch == '.' ||
        ch == '*';
}

// 设置由__t展开的区域的区域id
void fillField(tile __t, int fieldId)
{
        // 广度遍历的算法思想
        // 由__t为起点,依次遍历__t四周的所有位置
        // 可通行(道路或者房子),且没有被遍历过,纳入队列继续遍历
        // 被遍历过的所有可通行位置,就属于同一个区域。
    deque<tile> d;
    d.push_back(__t);
    while(d.size() != 0)
    {
        tile head = d.front();
        d.pop_front();
        field[head.j][head.i] = fieldId;
        
                // 左遍历
        int i = head.i - 1;
        int j = head.j;
        if (i >= 0 && !visit[j][i])
        {
            tile t(i, j, ts[j][i]);
            d.push_back(t);
        }
        
                // 右遍历
        i = head.i + 1;
        j = head.j;
        if (i < N && !visit[j][i])
        {
            tile t(i, j, ts[j][i]);
            d.push_back(t);
        }
        
                // 上遍历
        i = head.i;
        j = head.j - 1;
        if (j >= 0 && !visit[j][i])
        {
            tile t(i, j, ts[j][i]);
            d.push_back(t);
        }

                // 下遍历
         i = head.i;
        j = head.j + 1;
        if (j < N && !visit[j][i])
        {
            tile t(i, j, ts[j][i]);
            d.push_back(t);
        }
    }
}

// 穷举法找到最小距离之和
result* findMinFath(int fieldId)
{
        // 先把房子坐标找出
    int roomI[50] = {-1};
    int roomJ[50] = {-1};
    
    int index = 0;
    for(int i = 0; i < N; i++)
    {
        for (int j = 0; j < N; j++)
        {
            if (field[j][i] == fieldId && ts[j][i] == '#')
            {
                roomI[index] = i;
                roomJ[index++] = j;
            }
        }
    }
    
    if(index == 0)
                // 区域内没有房子
        return new result(false, 0);
    
    result* r = NULL;
    for (int i = 0; i < N; i++)
    {
        for(int j = 0; j < N; j++)
        {
            if (field[j][i] == fieldId)
            {
                            // 计算[j,i]位置到达所有房子的最小距离
                int path = 0;
                for(int xx = 0; xx < index; xx++)
                {
                    int diffI = roomI[xx] - i;
                    int diffJ = roomJ[xx] - j;
                    path += (diffI >= 0 ? diffI : -diffI);
                    path += (diffJ >= 0 ? diffJ : -diffJ);
                }
                
                if(r == NULL)
                    r = new result(true, path);
                else
                    r->minPath = min(r->minPath, path);
            }
        }
    }
    return r;
}

int main()
{
    cin >> N;
    for(int i = 0; i < N; i++)
    {
        for (int j = 0; j < N; j++)
        {
            cin >> ts[j][i];
            if(!isValidChar(ts[j][i]))
            {
                                // 输入错误直接返回
                cout << "0 0" << endl;
                return 0;
            }
        }
    }

    for(int i = 0; i < N; i++)
    {
        for (int j = 0; j < N; j++)
        {
                        // 不可通行位置直接设置true
            bool pass = ts[j][i] == '.' || ts[j][i] == '#';
            visit[j][i] = !pass;
        }
    }

    for(int i = 0; i < N; i++)
    {
                // 初始化
        for (int j = 0; j < N; j++)
            field[j][i] = -1;
    }
    
    vector<tile> fields;
    fields.clear();
    int fieldId = 0;
    for(int i = 0; i < N; i++)
    {
        for (int j = 0; j < N; j++)
        {
                        // 找出被隔离区域
            if(!visit[j][i])
            {
                tile t(i, j, ts[j][i]);
                fields.push_back(t);
                fillField(t, ++fieldId);
            }
        }
    }

    int path = 0;
    int super = 0;
    for(int i = 1; i <= fieldId; i++)
    {
        result* r = findMinFath(i);
                if (r->hasSuper)
        {
                    // 有房子需要建超市
                    super++;
                    path += r->minPath;
        }
        free(r);
    }
    
    cout << super;
    cout << " ";
    cout << path << endl;
    return 0;
}

编辑于 2022-06-01 12:12:52 回复(0)

问题信息

上传者:小小
难度:
6条回答 2712浏览

热门推荐

通过挑战的用户

安排超市