CF题解——Power of Points

本文最后更新于 9 个月前,文中所描述的信息可能已发生改变。

E. Power of Points 解题思路

核心问题分析与数学转化

本题要求对于给定的点集 X={x1,x2,,xn}X = \{x_1, x_2, \dots, x_n\},对集合中每一个 sXs \in X,计算总功率和 p=1109fp\sum_{p=1}^{10^9} f_p。其中 fpf_p 定义为与点 pp 相交的线段 [s,xi][s, x_i] 的数量。

1. 功率和与线段长度的关系

一个关键的观察是,一个线段 [a,b][a, b] (不妨设 aba \le b) 覆盖的整数点数量(即对总功率和 fp\sum f_p 的贡献)恰好是其长度 ba+1b - a + 1

线段 [s,xi][s, x_i] 的长度为:

Length([s,xi])=max(s,xi)min(s,xi)+1=sxi+1\text{Length}([s, x_i]) = |\max(s, x_i) - \min(s, x_i)| + 1 = |s - x_i| + 1

因此,总功率和 p=1109fp\sum_{p=1}^{10^9} f_p 实际上等于所有 nn 条线段长度之和:

Total Power(s)=i=1nLength([s,xi])=i=1n(sxi+1)\text{Total Power}(s) = \sum_{i=1}^{n} \text{Length}([s, x_i]) = \sum_{i=1}^{n} (|s - x_i| + 1)

总和可以简化为:

Total Power(s)=n+i=1nsxi\text{Total Power}(s) = n + \sum_{i=1}^{n} |s - x_i|

我们的核心任务,便转化成了对每一个 sXs \in X,高效计算 i=1nsxi\sum_{i=1}^{n} |s - x_i|

2. 离散化与结果缓存

由于输入点 xix_i 的坐标范围高达 10910^9,但点的数量 NN 较小(2105\le 2 \cdot 10^5),我们采用以下策略来优化计算:

  • 数据预处理:对原始输入进行排序,以便进行差分计算。
  • 结果缓存:使用 std::map<long long, long long> data_map 存储每个不重复ss 坐标及其对应的总功率和,以应对原始输入中存在的重复点。

核心优化:差分递推计算 sxi|s - x_i| 之和

为了实现 O(NlogN)O(N \log N) 的时间复杂度(主要耗费在排序上),我们使用差分(Differential)思想。

S(s)=j=1nsxjS(s) = \sum_{j=1}^{n} |s - x_j|。我们对排序后的去重点 sis_{i}si+1s_{i+1} 进行分析,差值 Δ=si+1si>0\Delta = s_{i+1} - s_i > 0。通过已知的 ScurrS_{curr} 来快速计算相邻点 SnextS_{next}

递推公式的建立

sssis_i 变化到 si+1s_{i+1} 时,每个 xjx_j 对总和的贡献变化量 Δj\Delta_j 为:

Δj=si+1xjsixj\Delta_j = |s_{i+1} - x_j| - |s_i - x_j|

  1. 对于 xjsix_j \le s_i (左侧):

    Δj=(si+1xj)(sixj)=si+1si=Δ\Delta_j = (s_{i+1} - x_j) - (s_i - x_j) = s_{i+1} - s_i = \Delta

    (贡献增加 Δ\Delta)

  2. 对于 xj>si+1x_j > s_{i+1} (右侧):

    Δj=(xjsi+1)(xjsi)=sisi+1=Δ\Delta_j = (x_j - s_{i+1}) - (x_j - s_i) = s_i - s_{i+1} = -\Delta

    (贡献减少 Δ\Delta)

递推关系:

S(si+1)=S(si)+j=1nΔjS(s_{i+1}) = S(s_i) + \sum_{j=1}^{n} \Delta_j

LLxjsix_j \le s_i 的点数,RRxj>si+1x_j > s_{i+1} 的点数。总和变化量 Diff\text{Diff} 为:

Diff=LΔRΔ=(LR)(si+1si)\text{Diff} = L \cdot \Delta - R \cdot \Delta = (L - R) \cdot (s_{i+1} - s_i)

3. 计算第一个点 s0s_0 的初始值

对于排序后的第一个点 s0=data[0]s_0 = data[0],所有 xjs0x_j \ge s_0,因此 s0xj=xjs0|s_0 - x_j| = x_j - s_0

j=1ns0xj=j=1n(xjs0)=(j=1nxj)ns0\sum_{j=1}^{n} |s_0 - x_j| = \sum_{j=1}^{n} (x_j - s_0) = \left(\sum_{j=1}^{n} x_j\right) - n \cdot s_0

初始总功率和 S0S_0 为:

S0=n+(j=1nxj)ns0S_0 = n + \left(\sum_{j=1}^{n} x_j\right) - n \cdot s_0


CPP 代码实现

cpp
#include <bits/stdc++.h>

using namespace std;

void solve() {

    // N: 数据点的数量
    long long data_number;
    // current_sum: 用于保存 Σ|s - x_i| 的当前值 (距离和)
    long long current_sum = 0;
    
    cin >> data_number;
    
    // data: 存储原始输入坐标,用于排序和递推
    vector<long long> data(data_number);
    // ans_order: 存储原始输入顺序,用于最后按原顺序输出
    vector<long long> ans_order(data_number); 
    
    // result_map: 存储去重后的 s 坐标及其对应的总功率和(结果)
    map<long long, long long> result_map;

    // 1. 读取数据并计算所有 x_i 的总和,同时保存原始顺序
    for (long long i = 0; i < data_number; i++) {
        cin >> data[i];
        ans_order[i] = data[i]; // 保存原始顺序
        current_sum += data[i]; // 累加所有 x_i (Σx_i)
    }

    // 2. 排序,为差分递推做准备
    sort(data.begin(), data.end());

    // 3. 计算第一个点 data[0] 的初始 Σ|s - x_i|
    // current_sum = (Σx_i) - n * data[0]
    current_sum = current_sum - data[0] * data_number; 
    
    // S_0 (总功率和) = Σ|s_0 - x_i| + n
    long long s0_total_power = data_number + current_sum; 
    
    // 将第一个点 data[0] 的结果存入 Map
    result_map[data[0]] = s0_total_power;

    // 4. 差分递推计算后续点 S_i 的结果
    for (long long i = 0; i < data_number - 1; i++) {
        if (data[i + 1] != data[i]) {
            // 变化量 Δ = data[i+1] - data[i]
            long long delta = data[i + 1] - data[i];
            
            // L: 左侧点数,即 x_j <= data[i] 的数量 (包含 data[0] 到 data[i])
            long long L = i + 1; 
            
            // R: 右侧点数,即 x_j > data[i+1] 的数量
            // R = 总点数 - (i + 2)
            long long R = data_number - (i + 2); 
            
            // 递推核心: S_next - S_curr = L * Δ - R * Δ
            long long diff = (L * delta) - (R * delta);

            // 更新 current_sum (Σ|s - x_i|)
            current_sum += diff;
            
            // 更新 S(s) 的总功率和 (Σ|s - x_i| + n)
            result_map[data[i + 1]] = current_sum + data_number;
        }
    }

    // 5. 按原始输入顺序输出结果
    for (long long i = 0; i < data_number; i++) {
        cout << result_map[ans_order[i]] << ' ';
    }

    cout << '\n';

}

int main() {

    // 优化 I/O 速度
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);

    long long test;
    cin >> test;

    while (test--) {
        solve();
    }
}
CF题解——Beautiful Sequence
CF题解——Trip to the Olympiad