题目描述
Shopee物流会有很多个中转站。在选址的过程中,会选择离用户最近的地方建一个物流中转站。
假设给你一个二维平面网格,每个格子是房子则为1,或者是空地则为0。找到一个空地修建一个物流中转站,使得这个物流中转站到所有的房子的距离之和最小。 能修建,则返回最小的距离和。如果无法修建,则返回 -1。
若范围限制在100*100以内的网格,如何计算出最小的距离和?
当平面网格非常大的情况下,如何避免不必要的计算?
输入描述
4 0 1 1 0 1 1 0 1 0 0 1 0 0 0 0 0 先输入方阵阶数,然后逐行输入房子和空地的数据,以空格分隔
输出描述
8
解题思路
不考虑暴力法,假设这是一个灰常灰常大的矩阵。
- 1、矩阵上的每行的的点到其他行上的用户的纵向距离是相同的,每列同理;
- 2、由1中结论,计算仓库到用户的距离是,可不必重复计算,采用缓存存储计算结果
- 3、计算距离的复杂度从
降低到
。【暴力法对每个可能
的位置都会计算距离,假设有个位置可放仓库,那么就需要对另外
个用户求距离。所以最坏情况下要计算次距离】
- 4、以下代码还是要遍历所有可能的位置,只是计算距离的次数少了。我们还可以引入等势线的概念:
首先计算所有用户的重心,然后以此为中心;如果中心可放仓库,那么一定是距离的最小的。否则,到
下一圈等势线上寻找,直到找到可放仓库的位置则停止算法。不过这个等势线不是很好找,有思路的同
学可以试一下,能有证明就更完美了。
代码
#include <iostream>
#include <vector>
#include <numeric>
#include <algorithm>
#include <limits>
using namespace std;
struct Pos{
int x;
int y;
Pos(int x_, int y_): x(x_), y(y_){}
Pos(): x(0), y(0){}
};
//求某一行(列)到其他所有行(列)的纵(横)向距离
int distance(vector<int>& d, size_t idx){
int res = 0;
for(int i = 0; i < idx; ++i) res += d[i] * (idx - i);
for(int i = idx + 1; i < d.size(); ++i) res += d[i] * (i - idx);
return res;
}
int shopee(vector<vector<int>>& mat){
int N = mat.size();
vector<int> row(N, 0);//计算每一行上的用户数
vector<int> col(N, N);//计算每一列上的用户数
//距离缓存-存储某一行(列)到其他所有行(列)的纵(横)向距离
vector<int> row_d(N, -1);
vector<int> col_d(N, -1);
//可放置仓库的位置
vector<Pos> pos; pos.resize(N << 2);
//循环求每一行(列)上的用户数
for(int i = 0; i < N; ++i){
row[i] = accumulate(mat[i].begin(), mat[i].end(), 0);
for(int j = 0; j < N; ++j) {
if(0 == mat[i][j]){
pos.push_back(Pos(i, j));
--col[j];
}
}
}
if(pos.empty()) return -1;
//开始求解
int res = numeric_limits<int>::max();
for(auto& p: pos){
//对于大型矩阵,可以提前算好距离存储,避免分支语句
if(row_d[p.x] < 0) row_d[p.x] = distance(row, p.x);//该位置所在行到其他行的纵向距离不在缓存中,需要计算
if(col_d[p.y] < 0) col_d[p.y] = distance(col, p.y);//同上
res = min(res, row_d[p.x] + col_d[p.y]);//比较结果
}
return res;
}
int main(){
int N;
cin >> N;
vector<vector<int>> mat(N, vector<int>(N, 0));
for(int i = 0; i < N; ++i)
for(int j = 0; j < N; ++j) cin >> mat[i][j];
cout << shopee(mat) << endl;
}
京公网安备 11010502036488号