我目前对动态规划还没有系统的学习过,所以说不上啥专业的术语,我的题解也是从别人的题解中受到了启发,然后加入了自己的理解。
其他几个解题报告有的的纯贴代码,有的题解看得不是很懂,我尽量用通俗易懂的方式展示自己的解题思路,希望能帮助到大家,同时也提升我自己。
最开始刷这道题的时候,状态不是很好,当时想着用穷举法试下运气。
穷举法比较简单,就是遍历每一块空地,求该空地的距离和,然后找出所有空地中距离和的最小值,我用的是PHP语言,反正穷举法是过不了,会超时(即使能过也没啥意思,毕竟没点技术含量)
后来简单的想了下,最小值的空地应该会靠近这些房子的几何上的中心点,所以当时就想着找到这个中心点,然后简单的把中心点以及中心点附近的空地都遍历一遍,找出这些点中的最优解。怀着试一试的心态,居然AC了,后面我自己都发现有边界的测试用例,说明牛客网的测试用例还是不全面。
有个大神也是类似的思路,他提出了“等势线“的概念,即中点是绝对的最优解,如果中点不能建,就以中点为中心,向外扩展一圈,找这一圈中的最优解,如果这一圈没空地,则继续再往外扩一圈。
我跟他的思路大体相同,只是还是没有在数学上得到证明,所以理论上有风险。
贴下AC的代码(该解决方案有问题):
<?php //调试开关,1为开启,开启则输出一些中间调试信息 define("DEBUG", 0); /** * 计算坐标(i1,j1)到坐标(i2,j2)的距离 * @param int $i1 * @param int $j1 * @param int $i2 * @param int $j2 */ function getDistance($i1, $j1, $i2, $j2) { return abs($i1 - $i2) + abs($j1 - $j2); } /** * 计算$i $j位置的空地到各个房子之间的距离和 * @param array $map 地图方阵阶数 * @param int $blankI 空地坐标i * @param int $blankJ 空地坐标j */ function getDistanceSum($map, $blankI, $blankJ) { $sum = 0; for ($i = 0; $i < count($map); $i++) { for ($j = 0; $j < count($map[$i]); $j++) { if ($map[$i][$j] === 1) { $sum += getDistance($blankI, $blankJ, $i, $j); } } } return $sum; } fscanf(STDIN, "%d", $num); $map = []; for ($i = 0; $i < $num; $i++) { $input = fgets(STDIN); $map[$i] = explode(' ', $input); } //将数据类型转成整形,php是弱类型语言,担心留坑 for ($i = 0; $i < $num; $i++) { for ($j = 0; $j < $num; $j++) { $map[$i][$j] = intval($map[$i][$j]); } } $shortDist = -1;//最小距离和 $houseCount = 0; $sumI = 0; $sumJ = 0; for ($i = 0; $i < $num; $i++) { for ($j = 0; $j < $num; $j++) { /* if ($map[$i][$j] === 0) { //计算当前位置的距离和 $distance = getDistanceSum($map, $i, $j); if (constant('DEBUG') == 1) { echo $i . " " . $j . ":" . $distance . "\n"; } if ($distance < $shortDist || $shortDist === -1) { $shortDist = $distance; } }*/ if ($map[$i][$j] === 1) { $houseCount++; $sumI += $i; $sumJ += $j; } } } $averageI = intval($sumI / $houseCount); $averageJ = intval($sumJ / $houseCount); if (constant('DEBUG') == 1) { echo 'average i is ' . $averageI . "\n"; echo 'average j is ' . $averageJ . "\n"; } for ($i = $averageI - 1; $i <= $averageI + 1 && $i < $num; $i++) { if ($i < 0) { continue; } for ($j = $averageJ - 1; $j <= $averageJ + 1 && $j < $num; $j++) { if ($j < 0) { continue; } if ($map[$i][$j] === 0) { //计算当前位置的距离和 $distance = getDistanceSum($map, $i, $j); if (constant('DEBUG') == 1) { echo $i . " " . $j . ":" . $distance . "\n"; } if ($distance < $shortDist || $shortDist === -1) { $shortDist = $distance; } } if ($map[$i][$j] === 1) { $houseCount++; $sumI += $i; $sumJ += $j; } } } echo $shortDist . "\n";
大家看上面的代码,其实很不严谨,比如如果没有一座房子,应该会有除以0的边界,如果中心点附近都是房子,没有空地,也会无解。但是居然AC了。
虽然AC了,但是内心没有任何高兴,因为这样通过没任何收获,后面参看了其他几个大神给出的解题报告,加入自己的理解,整出了自己的解决方案。
穷举法之所以会超时是因为遍历所有空地时间复杂度是O(N2),计算每块空地的距离和时间复杂度又是O(N2),所以总的时间复杂度变成了O(N2)XO(N2)=O(N4)。
优化思路是看能不能减少计算每块空地的距离和的时间复杂度,后面经过分析建立了这样的数学模型。
我们建立一个如上图所示的平面直角坐标系,坐标系的左上角为原点,房子或空地坐落在网格上。
我们假定在原点建立物流中转站(即使原点是房子也没关系,不影响计算),假设各个房子到原点的距离和是distance00(可以遍历所有点算出来,这一步省不了),那么在(1,0)坐标出建立中转站的距离和能否便捷的算出来呢。
我们分析发现,将物流中转站从坐标(0,0)迁移到坐标(0,1)时,纵坐标方向上的距离没有任何影响,但是对于横坐标方向的距离有影响,影响如下:
对于横坐标x<=0的房子来说,距离增加了1,对于x>0的房子来说,距离减小了1,因此(0,1)坐标处的距离和为distance00+(x<=0的房子数量)-(x>0的房子数量),为了描述方便,
我们设房子的总数量为house,原点所在线及左边(x<=0)的房子的数量为a0,原点线右边(x>0)的房子总数为house-a0(左边房子数加右边房子数等于房子总数),
那么上面的公式就可以简化为:(0,1)坐标处的距离和为:distance00+a0-(house-a0)=distance+2*a0-house。
利用这个公式就是快速的计算出(0,1),(0,2)......(0,num)坐标的距离。
同样的道理,如果把物流中转站从坐标(0,0)迁移到坐标(1,0)时,假设y<=0的房子数量为b0,则(1,0)处坐标的距离和为:distance00+2*b0-house。
这样,我们只要能统计出a0,a1......anum,b0,b1......bnum的数据,然后计算出(0,0)位置的坐标距离和,就能快速计算出其他位置的距离和。
相对于穷举法,这种计算方式的时间复杂度是O(1)级别(就是四则运算)。
统计出a0,a1......anum,b0,b1......bnum的数据应该比较简单,建立两个辅助数组即可。
贴上代码:
<?php /** *调试开关,1为开启,开启则输出一些中间调试信息 */ define("DEBUG", 0); class Solution { /** * @var array 地图方阵数据 */ private $map = []; /** * @var array x轴的辅助数组,记录x<=i的房子的总数,对应解题报告中的ai */ private $xHelp = []; /** * @var array y轴的辅助数组,记录x<=j的房子的总数,对应解题报告中的bj */ private $yHelp = []; /** * @var array 各个位置的距离值 */ private $distances = []; /** * @var int 正方形的边长 */ private $num = 0; /** * @var int 房子数量,即矩阵中1的总数 */ private $house = 0; public function __construct() { $this->init(); $this->calDistances(); } /** * 初始化辅助数组 */ private function initHelp() { for ($i = 0; $i < $this->num; $i++) { $this->xHelp[$i] = []; $this->yHelp[$i] = []; for ($j = 0; $j < $this->num; $j++) { $this->xHelp[$i][$j] = 0; $this->yHelp[$i][$j] = 0; } } } /** * 初始化距离和结果数组(各个点的结果) */ private function initDistances() { for ($i = 0; $i < $this->num; $i++) { $this->distances[$i] = []; for ($j = 0; $j < $this->num; $j++) { $this->distances[$i][$j] = 0; } } } /** *初始化 */ private function init() { $this->getInput(); $this->initHelp(); $this->initDistances(); //将数据类型转成整形,php是弱类型语言,担心留坑 for ($i = 0; $i < $this->num; $i++) { for ($j = 0; $j < $this->num; $j++) { $this->map[$i][$j] = intval($this->map[$i][$j]); if ($this->map[$i][$j] === 1) { $this->house++; $this->markXHelp($i); $this->markYHelp($j); } } } } /** * 计算坐标(i1,j1)到坐标(i2,j2)的距离 * @param int $i1 * @param int $j1 * @param int $i2 * @param int $j2 */ private function getDistance($i1, $j1, $i2, $j2) { return abs($i1 - $i2) + abs($j1 - $j2); } /** * 计算$i $j位置区域到各个房子之间的距离和 * @param int $blankI 空地坐标i * @param int $blankJ 空地坐标j */ private function getDistanceSum($blankI, $blankJ) { $sum = 0; for ($i = 0; $i < $this->num; $i++) { for ($j = 0; $j < $this->num; $j++) { if ($this->map[$i][$j] === 1) { $sum += $this->getDistance($blankI, $blankJ, $i, $j); } } } return $sum; } /** * 如果X轴$i位置有房子,则xHelp数组大于等于位$houseI置的元素值都需要加1 * @param $houseI */ private function markXHelp($houseI) { for ($i = $houseI; $i < $this->num; $i++) { for ($j = 0; $j < $this->num; $j++) { $this->xHelp[$i][$j]++; } } } /** * @param $houseJ */ private function markYHelp($houseJ) { for ($i = 0; $i < $this->num; $i++) { for ($j = $houseJ; $j < $this->num; $j++) { $this->yHelp[$i][$j]++; } } } /** * 获取输入数据 */ private function getInput() { fscanf(STDIN, "%d", $this->num); for ($i = 0; $i < $this->num; $i++) { $input = fgets(STDIN); $this->map[$i] = explode(' ', $input); } } /** * 根据公式计算各个点的距离和,详见解题报告中公式的推导过程 */ private function calDistances() { $this->distances[0][0] = $this->getDistanceSum(0, 0); for ($i = 0; $i < $this->num; $i++) { if ($i > 0) { $this->distances[$i][0] = $this->distances[$i - 1][0] + 2 * $this->xHelp[$i - 1][0] - $this->house; } for ($j = 1; $j < $this->num; $j++) { $this->distances[$i][$j] = $this->distances[$i][$j - 1] + 2 * $this->yHelp[$i][$j - 1] - $this->house; } } } /** * 根据矩阵数据,计算最小的距离和 * @return int */ public function getMinDistSum() { $shortDist = -1;//最小距离和 for ($i = 0; $i < $this->num; $i++) { for ($j = 0; $j < $this->num; $j++) { if ($this->map[$i][$j] === 0) { $distance = $this->distances[$i][$j]; if (constant('DEBUG') == 1) { echo $i . " " . $j . ":" . $distance . "\n"; } if ($distance < $shortDist || $shortDist === -1) { $shortDist = $distance; } } } } return $shortDist; } } $solution = new Solution(); $minDistSum = $solution->getMinDistSum(); echo $minDistSum . "\n";再贴下我自己的几个测试用例:
Input1: 5 1 1 1 1 1 1 0 0 0 1 1 0 0 0 1 1 0 0 0 1 1 1 1 1 1 Output1: 48 Input2: 5 1 1 1 1 1 1 0 0 0 1 1 0 1 0 1 1 0 0 0 1 1 1 1 1 1 Output2: 51 Input3: 4 0 1 1 0 1 1 0 1 0 0 1 0 0 0 0 0 Output3: 8 Input4: 7 1 1 1 1 1 1 1 1 0 0 0 0 0 1 1 0 1 1 1 0 1 1 0 1 1 1 0 1 1 0 1 1 1 0 1 1 0 0 0 0 0 1 1 1 1 1 1 1 1 Output4: 140 Input5: 7 1 1 1 1 1 1 1 1 0 0 1 0 0 1 1 0 1 1 1 0 1 1 0 1 1 1 0 1 1 0 1 1 1 0 1 1 0 0 1 0 0 1 1 1 1 1 1 1 1 Output5: 148
本人致力于编写通俗易懂的解题报告,如果对上面有任何没看明白的地方,请直接留言回复。我会尽我所能完善解题报告。
我建立了一个git仓库专门存放代码,以及测试用例等资料,仓库地址:https://github.com/S-Knight/nowcoder
如果你觉得本文对您有帮助,记得给我点赞哦,你们的点赞是我持续更新的动力。