集训笔记:ECNA 2023-2024 B-B Road Band题解

题目类型: 线性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} 1e5

​ 数据范围: m ( m ≤ 1000 ) m(m\le1000) m(m1000) n ( n ≤ 1000 ) n(n\le1000) n(n1000) x ( 0 ≤ x ≤ 1000 ) x(0\le x \le 1000) x(0x1000) k ( k ≤ 100 ) k(k \le 100) k(k100)

模型

​ 根据题意,基站一定在中间,所以在两岸的点是等价的。

​ 因此可以把这个题的两岸的点转移到一个岸上,即线上修建 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} x1xn+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用剩下一个基站。

点数\基站数量12k
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][j1]+ask(g+1,i),1<ji,1g<idp[i][j1],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=((aix)2+(2s)2)=ai22aix+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=1kai22i=1kaix+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=ki=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(aavg22aavgx+x2)=k(aavgx)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=1n+m(aix)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(a1x)+2(a2x)+...+2(aix)

​ 令 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=ij=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;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值