题目类型: 线性dp,数学
题意:
给定两条平行轴上的点,两条轴的距离是 s s s。第一条轴上给定 m m m个点,第二条轴上给定 n n n个点,点坐标 x x x。在距两条轴距离相等且平行的线上,标记 k k k个基站,
求 n + m n+m n+m个点到最近的基站的距离平方和。要求最小,并输出这个值,误差不超过 1 e − 5 1e^{-5} 1e−5
数据范围: m ( m ≤ 1000 ) m(m\le1000) m(m≤1000), n ( n ≤ 1000 ) n(n\le1000) n(n≤1000), x ( 0 ≤ x ≤ 1000 ) x(0\le x \le 1000) x(0≤x≤1000), k ( k ≤ 100 ) k(k \le 100) k(k≤100)
模型
根据题意,基站一定在中间,所以在两岸的点是等价的。
因此可以把这个题的两岸的点转移到一个岸上,即线上修建 k k k个点,轴上 n + m n+m n+m个点,到线的距离为 s 2 \frac s2 2s,求出每个点到最近基站的距离平方的和最小。
那么每个修建的基站对应的是一段连续的点的区间,枚举每一个修建基站对应了哪一段点。使用线性dp模型求解此题。
最优子结构
设点的序列X =< x 1 , x 2 , . . . , x n + m x_1, x_2, ..., x_{n + m} x1,x2,...,xn+m >,基站的序列是Y = < y 1 , y 2 , . . . , y k y_1, y_2,..., y_k y1,y2,...,yk>。因此, f i , j f_{i, j} fi,j表示前i个点建j个基站的最小距离平方和,满足下列条件
1.如果只有一个基站(j = 1),则点序列( x 1 − x n + m x_1 - x_{n + m} x1−xn+m)全部连接一个基站。
2.如果有多个基站并且点数大于基站数(j > 1 && i > j),则不需要再建立新的基站, f i , j f_{i,j} fi,j = f i , i f_{i,i} fi,i
3.如果有多个基站并且点数不大于基站数(j > 1 && i <= j),则枚举点序列X1 =< x 1 , x 2 , . . . x j x_1,x_2,...x_j x1,x2,...xj> k < i k \lt i k<i ,序列X2 =< x k + 1 , x k + 2 , . . . x i x_{k + 1},x_{k + 2},...x_i xk+1,xk+2,...xi> 序列X1用j - 1个基站,序列X2用剩下一个基站。
点数\基站数量 | 1 | 2 | … | k |
---|---|---|---|---|
1 | f 1 , 1 f_{1,1} f1,1 | f 1 , 2 f_{1,2} f1,2 | … | f 1 , k f_{1,k} f1,k |
2 | f 2 , 1 f_{2,1} f2,1 | f 2 , 2 f_{2,2} f2,2 | … | f 2 , k f_{2,k} f2,k |
… | … | … | … | … |
n + m | f n + m , 1 f_{n+m,1} fn+m,1 | f n + m , 2 f_{n+m,2} fn+m,2 | … | f n + m , k f_{n+m,k} fn+m,k |
递归解
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 表从第
1
1
1个点到第
i
i
i个点,建
j
j
j个基站的最小距离平方和。
d
p
[
i
]
[
j
]
=
{
a
s
k
(
1
,
i
)
,
j
=
1
d
p
[
g
]
[
j
−
1
]
+
a
s
k
(
g
+
1
,
i
)
,
1
<
j
≤
i
,
1
≤
g
<
i
d
p
[
i
]
[
j
−
1
]
,
j
>
i
dp[i][j] = \begin{cases} ask(1, i) , j = 1 \\ dp[g][j - 1] + ask(g + 1, i) ,1 \lt j \le i, 1 \le g \lt i\\ dp[i][j - 1], j > i \end{cases}
dp[i][j]=⎩
⎨
⎧ask(1,i),j=1dp[g][j−1]+ask(g+1,i),1<j≤i,1≤g<idp[i][j−1],j>i
其中,
a
s
k
(
s
t
,
e
d
)
ask(st, ed)
ask(st,ed) 表示从
s
t
st
st点到
e
d
ed
ed点,建一个基站,最小距离平方和。
时间复杂度
子问题状态n+m * k 个,状态转移n+m次,复杂度为O((n+m) * k * (n+m))
实现
ask函数
由勾股定理得,点到基站的距离
y
=
∑
(
(
a
i
−
x
)
2
+
(
s
2
)
2
)
=
∑
a
i
2
−
2
∑
a
i
∗
x
+
∑
x
2
+
∑
(
s
2
)
2
\begin{align} y &= \sum\left(\left(a_i - x\right) ^ 2 + \left(\frac s2\right)^ 2\right)\\ &= \sum a_i ^ 2 - 2\sum a_i * x + \sum x ^ 2 + \sum\left(\frac s2\right) ^ 2 \end{align}
y=∑((ai−x)2+(2s)2)=∑ai2−2∑ai∗x+∑x2+∑(2s)2
结论:一段点建立基站的位置是这几个点的坐标平均值。
证明方法一:
对k个点,有
y
k
=
∑
i
=
1
k
a
i
2
−
2
∑
i
=
1
k
a
i
∗
x
+
∑
x
2
+
k
∗
(
s
2
)
2
y_k = \sum_{i = 1}^{k} a_i ^ 2 - 2\sum_{i = 1}^{k} a_i * x + \sum x ^ 2 + k * (\frac s2) ^ 2
yk=i=1∑kai2−2i=1∑kai∗x+∑x2+k∗(2s)2
令
a
a
v
g
=
∑
i
=
1
k
a
i
k
a_{avg} = \frac{\sum_{i = 1}^{k} a_i}{k}
aavg=k∑i=1kai
y
k
=
k
(
a
a
v
g
2
−
2
∗
a
a
v
g
∗
x
+
x
2
)
=
k
(
a
a
v
g
−
x
)
2
y_k=k(a_{avg} ^ 2 - 2 * a_{avg} * x + x ^ 2) = k(a_{avg} - x) ^ 2
yk=k(aavg2−2∗aavg∗x+x2)=k(aavg−x)2
显然
y
k
y_k
yk是一个开口向上的二次函数。最小值点
x
=
a
a
v
g
x = a_{avg}
x=aavg
证明方法二:
对k个点,有
f
k
=
∑
i
=
1
n
+
m
(
a
i
−
x
)
2
+
(
s
2
)
2
f_k = \sum_{i = 1}^{n + m} (a_i - x) ^ 2 + (\frac s2) ^ 2
fk=i=1∑n+m(ai−x)2+(2s)2
f ′ = 2 ( a 1 − x ) + 2 ( a 2 − x ) + . . . + 2 ( a i − x ) f' = 2(a_1 - x) + 2(a_2 - x) + ... + 2(a_i - x) f′=2(a1−x)+2(a2−x)+...+2(ai−x)
令
f
′
=
0
f' = 0
f′=0,则
x
0
=
∑
j
=
1
i
a
j
i
x_0 = \frac {\sum_{j = 1}^{i}a_j}{i}
x0=i∑j=1iaj
并且,在
x
0
x_0
x0左边
f
′
<
0
f' < 0
f′<0递减,
x
0
x_0
x0右边
f
′
>
0
f' > 0
f′>0,因此
x
0
x_0
x0是极小值点。
对于y我们只要维护 ∑ a i 2 \sum a_i ^ 2 ∑ai2 , ∑ a i \sum a_i ∑ai , ( s 2 ) 2 \left(\frac s2\right) ^ 2 (2s)2 ,即前缀和与前缀平方和。
完整代码
#include <bits/stdc++.h>
using namespace std;
void solve() {
int m, n, k;
double s;
cin >> m >> n >> k >> s;
vector<double> pre(n + m + 10, 0), pre2(n + m + 10, 0);
vector<double> a(n + m + 10);
for (int i = 1; i <= n + m; i++) {
cin >> a[i];
}
sort(a.begin(), a.begin() + n + m + 1);
for (int i = 1; i <= n + m; i++) {
pre[i] = pre[i - 1] + a[i];
pre2[i] = pre2[i - 1] + a[i] * a[i];
}
vector<vector<double>> dp(n + m + 10, vector<double>(k + 10));
auto ask =[&] (int st, int ed) {
int num = ed - st + 1;
double add1 = pre[ed] - pre[st - 1];
double add2 = pre2[ed] - pre2[st - 1];
double add3 = (s / 2) * (s / 2) * num;
double x = add1 / num;
double sub = 2 * x * add1;
double res = num * x * x - sub + add2 + add3;
return res;
};
for (int i = 1; i <= n + m; i++) {
dp[i][1] = ask(1, i);
for (int j = 2; j <= k; j++) {
dp[i][j] = ask(1, i);
for (int g = 1; g < i; g++) {
dp[i][j] = min(dp[i][j], dp[g][j - 1] + ask(g + 1, i));
}
}
}
cout << fixed << setprecision(9);
cout << dp[n + m][k] << "\n";
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while(T--) {
solve();
}
return 0;
}