UVa 11370 Moogle

题目描述

你正在为 Maple mPhone\texttt{Maple mPhone}Maple mPhone 开发一款名为 Moogle Maps\texttt{Moogle Maps}Moogle Maps 的地图软件。 该软件需要能够显示像“主街 131313 号”这样的房屋地址位置。 但由于手机存储容量有限, 你不能存储每个房屋的精确位置, 而是只存储一个子集的位置, 其余房屋的位置通过线性插值得到。 你的目标是选择要存储的房屋位置, 使得所有房屋的平均插值误差最小。 街道被视为一条直线, 并且你必须始终存储第一个和最后一个房屋的位置。

给定你存储了房屋 iiijjj 的位置 xix_ixixjx_jxj , 且它们之间没有存储其他房屋, 则对于房屋编号 kkk (满足 i<k<ji < k < ji<k<j ), 其插值位置为:

xi+(xj−xi)⋅k−ij−i x_i + (x_j - x_i) \cdot \frac{k-i}{j-i} xi+(xjxi)jiki

输入格式

第一行包含一个整数 ttt1≤t≤501 \leq t \leq 501t50 ), 表示测试用例的数量。

每个测试用例包含两行。 第一行包含两个整数 hhhccc2≤h≤2002 \leq h \leq 2002h2002≤c≤h2 \leq c \leq h2ch ), 其中 hhh 是街道上的房屋数量, ccc 是可以存储的房屋位置数量。 第二行包含 hhh 个按递增顺序排列的整数, 表示每个房屋的位置, 每个位置在区间 [0,1000000][0, 1000000][0,1000000] 内。

输出格式

对于每个测试用例, 输出在最优选择 ccc 个房屋位置存储的情况下, 所有 hhh 个房屋的平均插值误差。 输出应保留四位小数, 允许 ±0.001\pm 0.001±0.001 的误差。

题目分析

这是一个典型的动态规划问题, 需要在必须选择第一个和最后一个房屋位置的约束下, 从 hhh 个点中选择 ccc 个点进行存储, 使得所有点的插值误差(绝对误差)的平均值最小。

核心思路

  1. 误差计算 : 如果选择存储房屋 iiijjji<ji < ji<j ), 且它们之间没有其他存储点, 则对于中间的任何房屋 kkki<k<ji < k < ji<k<j ), 其插值误差为 ∣xk−(xi+(xj−xi)⋅k−ij−i)∣\lvert x_k - (x_i + (x_j - x_i) \cdot \frac{k-i}{j-i}) \rvertxk(xi+(xjxi)jiki)∣ 。 我们可以预先计算任意两点 iiijjj 作为相邻存储点时, 中间所有房屋的误差和, 记为 error[i][j]error[i][j]error[i][j]

  2. 动态规划状态定义 : 定义 dp[i][k]dp[i][k]dp[i][k] 表示以房屋 iii 作为第 kkk 个被存储的点时, 前 iii 个房屋(包括 iii )的最小总误差和。 这里“第 kkk 个”意味着我们总共选择了 kkk 个存储点, 并且最后一个点正好是 iii

  3. 状态转移方程 : 要计算 dp[i][k]dp[i][k]dp[i][k] , 我们需要考虑上一个存储点 jjjj<ij < ij<i ), 且 jjj 是第 k−1k-1k1 个存储点。 那么从 jjjiii 之间的房屋(不包括 jjjiii )的误差就是 error[j][i]error[j][i]error[j][i] 。 因此, 状态转移方程为:
    dp[i][k]=min⁡0≤j<i{dp[j][k−1]+error[j][i]} dp[i][k] = \min_{0 \leq j < i} \{ dp[j][k-1] + error[j][i] \} dp[i][k]=0j<imin{dp[j][k1]+error[j][i]}

  4. 边界条件 : 由于第一个房屋必须被存储, 所以 dp[0][1]=0dp[0][1] = 0dp[0][1]=0 。 其他状态初始化为一个很大的值(表示不可达或误差无穷大)。

  5. 最终答案 : 由于最后一个房屋也必须被存储, 我们需要的是 dp[h−1][c]dp[h-1][c]dp[h1][c] , 即最后一个房屋是第 ccc 个存储点时的最小总误差。 平均误差即为 dp[h−1][c]/hdp[h-1][c] / hdp[h1][c]/h

算法步骤

  1. 读取输入数据。
  2. 对于每个测试用例:
    • 读取 hhh , ccc 和房屋位置数组 loclocloc
    • 预处理计算 errorerrorerror 矩阵: 对于所有 0≤i<j<h0 \leq i < j < h0i<j<h , 计算 error[i][j]error[i][j]error[i][j]
    • 初始化 dpdpdp 数组为无穷大, 设置 dp[0][1]=0dp[0][1] = 0dp[0][1]=0
    • 进行动态规划: 遍历 iii000h−1h-1h1kkk111ccc , 对于每个合法的 dp[i][k]dp[i][k]dp[i][k] , 尝试将其状态转移到所有 j>ij > ij>i 作为下一个存储点。
    • 计算平均误差 dp[h−1][c]/hdp[h-1][c] / hdp[h1][c]/h 并输出。
  3. 输出结果保留四位小数。

复杂度分析

  • 时间复杂度 : 预处理 errorerrorerror 矩阵需要 O(h3)O(h^3)O(h3) , 动态规划需要 O(h2c)O(h^2 c)O(h2c) 。 由于 h≤200h \leq 200h200c≤hc \leq hch , 总计算量在可接受范围内。
  • 空间复杂度 : 需要 O(h2)O(h^2)O(h2) 存储 errorerrorerror 矩阵和 O(h×c)O(h \times c)O(h×c) 存储 dpdpdp 数组。

代码实现

// Moogle
// UVa ID: 11370
// Verdict: Accepted
// Submission Date: 2025-12-20
// UVa Run Time: 0.040s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net

#include <bits/stdc++.h>
using namespace std;

double solve() {
    int h, c;
    cin >> h >> c;
    vector<double> loc(h);
    for (int i = 0; i < h; i++) cin >> loc[i];
    
    // 计算误差矩阵:error[i][j] 表示存储点 i 和 j 之间(不包括 i, j)的误差和
    vector<vector<double>> error(h, vector<double>(h, 0.0));
    for (int i = 0; i < h; i++) {
        for (int j = i + 1; j < h; j++) {
            double sum = 0.0;
            for (int k = i + 1; k < j; k++) {
                // 计算房屋 k 的插值位置
                double interp = loc[i] + (loc[j] - loc[i]) * (k - i) / double(j - i);
                sum += fabs(interp - loc[k]); // 累加绝对误差
            }
            error[i][j] = sum;
        }
    }
    
    // dp[i][k]: 以房屋 i 作为第 k 个存储点的最小误差和
    vector<vector<double>> dp(h, vector<double>(c + 1, 1e30));
    dp[0][1] = 0.0; // 第一个房屋是第一个存储点,误差为0
    
    // 动态规划
    for (int i = 0; i < h; i++) {
        for (int k = 1; k <= c; k++) {
            if (dp[i][k] > 1e29) continue; // 不可达状态
            // 尝试将 i 作为当前最后一个存储点,选择下一个存储点 j
            for (int j = i + 1; j < h; j++) {
                if (k + 1 <= c) {
                    dp[j][k + 1] = min(dp[j][k + 1], dp[i][k] + error[i][j]);
                }
            }
        }
    }
    
    // 最后一个房屋必须是第 c 个存储点
    return dp[h - 1][c] / h;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout << fixed << setprecision(4);
    
    int t;
    cin >> t;
    while (t--) cout << solve() << "\n";
    
    return 0;
}

总结

本题的关键在于将问题转化为一个动态规划模型, 并正确处理必须选择首尾房屋的约束条件。 通过预处理任意两点作为相邻存储点时的误差和, 我们可以高效地进行状态转移。 算法的时间复杂度对于题目给定的数据范围是完全可行的。 注意在实现时, 要使用 double 类型来存储误差值, 并在输出时控制精度。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值