工厂落点最小加权路程(C++/Py/Java/Js/Go)题解
华为笔试真题 8月26号 非AI方向第一题 100分题型
题目内容
现在要在二维平面上选定一个工厂落点 (P,Q)(P, Q)(P,Q),目标是让所有居民走到工厂的加权曼哈顿距离之和尽可能小。请输出这个最小值。
该地区共有 mmm 个居民区。第 iii 个居民区位于 (ai,bi)(a_i, b_i)(ai,bi),住有 wiw_iwi 名居民。
两点之间的路程按曼哈顿距离计算:(s,t)(s, t)(s,t) 到 (u,v)(u, v)(u,v) 为 ∣s−u∣+∣t−v∣|s-u|+|t-v|∣s−u∣+∣t−v∣。第 iii 个居民区对工厂的加权曼哈顿距离为
wi×(∣ai−P∣+∣bi−Q∣)w_i \times \bigl(|a_i-P|+|b_i-Q|\bigr)wi×(∣ai−P∣+∣bi−Q∣)
解答要求:
- 时间限制:C/C++ 100100100ms,其他语言:200200200ms
- 内存限制:C/C++ 256256256MB,其他语言:512512512MB
输入描述
第一行一个整数 mmm(1≤m≤100001 \le m \le 100001≤m≤10000),表示居民区个数。
接下来 mmm 行,每行三个整数 aia_iai、bib_ibi、wiw_iwi(−109≤ai,bi≤109-10^9 \le a_i, b_i \le 10^9−109≤ai,bi≤109,1≤wi≤1061 \le w_i \le 10^61≤wi≤106),表示第 iii 个居民区的坐标和居民人数。
输出描述
输出一个整数,表示最小的加权曼哈顿距离之和。
样例1
输入
3
0 3 2
5 3 4
5 7 1
输出
14
说明
把工厂建在 (5,3)(5, 3)(5,3) 时:
- 到 (0,3)(0, 3)(0,3) 的距离是 ∣0−5∣+∣3−3∣=5|0-5|+|3-3|=5∣0−5∣+∣3−3∣=5,人数 222,贡献 5×2=105 \times 2 = 105×2=10
- 到 (5,3)(5, 3)(5,3) 的距离是 000,人数 444,贡献 000
- 到 (5,7)(5, 7)(5,7) 的距离是 ∣5−5∣+∣7−3∣=4|5-5|+|7-3|=4∣5−5∣+∣7−3∣=4,人数 111,贡献 444
- 总和为 10+0+4=1410+0+4=1410+0+4=14,这就是最小的加权曼哈顿距离之和
样例2
输入
3
0 1 1
4 1 1
2 6 1
输出
9
说明
把工厂建在 (2,1)(2, 1)(2,1) 时:
- 到 (0,1)(0, 1)(0,1) 的距离是 222,贡献 222
- 到 (4,1)(4, 1)(4,1) 的距离是 222,贡献 222
- 到 (2,6)(2, 6)(2,6) 的距离是 555,贡献 555
- 总和为 2+2+5=92+2+5=92+2+5=9,这就是最小的加权曼哈顿距离之和
题解和思路
思路
实现思路:数学原理
- 要求∑i=1mwi(∣ai−P∣+∣bi−Q∣)\sum_{i=1}^{m} w_i\left(\lvert a_i-P\rvert+\lvert b_i-Q\rvert\right)∑i=1mwi(∣ai−P∣+∣bi−Q∣)可以拆分为∑i=1mwi∣ai−P∣+∑i=1mwi∣bi−Q∣\sum_{i=1}^{m}w_i\lvert a_i-P\rvert+\sum_{i=1}^{m}w_i\lvert b_i-Q\rvert∑i=1mwi∣ai−P∣+∑i=1mwi∣bi−Q∣ ,这样就能二维问题转换为一维问题,分别求出最优的P和Q在对应轴上和最小。
- 而一维问题的解决方案就是
加权中位数,以x轴为例,计算对应轴上总权重,将所有ai从小到大排序,将坐标从小到大移动,当左侧累计权重第一次达到总权重一半的位置,就是对应轴能取的最优位置。 - 按照1、2计算出对应P 和 Q,然后计算权重即可。
- 算法总体时间复杂度为
O(logm)
C++
cpp
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
struct Node {
// 位置
ll pos;
// 权重
ll w;
};
// 寻找加权中位数
ll getMedian(vector<Node> v) {
// 坐标升序
sort(v.begin(), v.end(), [](const Node& a, const Node& b) {
return a.pos < b.pos;
});
ll total = 0;
for (auto &x : v) {
total += x.w;
}
ll sum = 0;
for (auto &x : v) {
sum += x.w;
if (sum * 2 >= total) {
return x.pos;
}
}
return 0;
}
int main() {
int m;
cin >> m;
vector<Node> xs(m), ys(m);
for (int i = 0; i < m; i++) {
ll a,b,w;
cin >> a >> b >> w;
xs[i] = {a, w};
ys[i] = {b, w};
}
ll P = getMedian(xs);
ll Q = getMedian(ys);
ll ans = 0;
for (int i = 0; i < m; i++) {
ans += xs[i].w * llabs(xs[i].pos - P);
ans += ys[i].w * llabs(ys[i].pos - Q);
}
cout << ans;
return 0;
}
Java
java
import java.io.*;
import java.util.*;
public class Main {
static class Node {
// 位置
long pos;
// 权重
long w;
Node(long pos, long w) {
this.pos = pos;
this.w = w;
}
}
// 寻找加权中位数
static long getMedian(Node[] v) {
// 坐标升序
Arrays.sort(v, Comparator.comparingLong(a -> a.pos));
long total = 0;
for (Node x : v) {
total += x.w;
}
long sum = 0;
for (Node x : v) {
sum += x.w;
if (sum * 2 >= total) {
return x.pos;
}
}
return 0;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int m = sc.nextInt();
Node[] xs = new Node[m];
Node[] ys = new Node[m];
for (int i = 0; i < m; i++) {
long a = sc.nextLong();
long b = sc.nextLong();
long w = sc.nextLong();
xs[i] = new Node(a, w);
ys[i] = new Node(b, w);
}
long P = getMedian(xs);
long Q = getMedian(ys);
long ans = 0;
for (int i = 0; i < m; i++) {
ans += xs[i].w * Math.abs(xs[i].pos - P);
ans += ys[i].w * Math.abs(ys[i].pos - Q);
}
System.out.println(ans);
}
}
python
python
import sys
# 寻找加权中位数
def get_median(v):
# 坐标升序
v.sort(key=lambda x: x[0])
total = 0
for pos, w in v:
total += w
sum_w = 0
for pos, w in v:
sum_w += w
if sum_w * 2 >= total:
return pos
return 0
data = list(map(int, sys.stdin.buffer.read().split()))
idx = 0
m = data[idx]
idx += 1
xs = []
ys = []
for _ in range(m):
a = data[idx]
b = data[idx + 1]
w = data[idx + 2]
idx += 3
# 位置、权重
xs.append([a, w])
ys.append([b, w])
P = get_median(xs)
Q = get_median(ys)
ans = 0
for i in range(m):
ans += xs[i][1] * abs(xs[i][0] - P)
ans += ys[i][1] * abs(ys[i][0] - Q)
print(ans)
Javascript
js
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
let input = [];
rl.on('line', line => {
input.push(...line.trim().split(/\s+/));
});
rl.on('close', () => {
let idx = 0;
const m = Number(input[idx++]);
const xs = [];
const ys = [];
for (let i = 0; i < m; i++) {
const a = BigInt(input[idx++]);
const b = BigInt(input[idx++]);
const w = BigInt(input[idx++]);
xs.push({ pos: a, w: w });
ys.push({ pos: b, w: w });
}
// 寻找加权中位数
function getMedian(v) {
// 坐标升序
v.sort((a, b) => a.pos < b.pos ? -1 : a.pos > b.pos ? 1 : 0);
let total = 0n;
for (const x of v) {
total += x.w;
}
let sum = 0n;
for (const x of v) {
sum += x.w;
if (sum * 2n >= total) {
return x.pos;
}
}
return 0n;
}
const P = getMedian(xs);
const Q = getMedian(ys);
let ans = 0n;
for (let i = 0; i < m; i++) {
ans += xs[i].w * abs(xs[i].pos - P);
ans += ys[i].w * abs(ys[i].pos - Q);
}
console.log(ans.toString());
function abs(x) {
return x < 0n ? -x : x;
}
});
Go
go
package main
import (
"bufio"
"fmt"
"os"
"sort"
)
type Node struct {
// 位置
pos int64
// 权重
w int64
}
// 寻找加权中位数
func getMedian(v []Node) int64 {
// 坐标升序
sort.Slice(v, func(i, j int) bool {
return v[i].pos < v[j].pos
})
var total int64
for _, x := range v {
total += x.w
}
var sum int64
for _, x := range v {
sum += x.w
if sum*2 >= total {
return x.pos
}
}
return 0
}
func abs(x int64) int64 {
if x < 0 {
return -x
}
return x
}
func main() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
var m int
fmt.Fscan(in, &m)
xs := make([]Node, m)
ys := make([]Node, m)
for i := 0; i < m; i++ {
var a, b, w int64
fmt.Fscan(in, &a, &b, &w)
xs[i] = Node{a, w}
ys[i] = Node{b, w}
}
P := getMedian(xs)
Q := getMedian(ys)
var ans int64
for i := 0; i < m; i++ {
ans += xs[i].w * abs(xs[i].pos-P)
ans += ys[i].w * abs(ys[i].pos-Q)
}
fmt.Fprintln(out, ans)
}