题目描述
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; }