[HNOI2002] 营业额统计

GitHub跳转原题关系图返回列表

离线压缩营业额,用 Fenwick 树动态寻找已出现值中的前驱和后继并累加最近差。

OJ: luogu

题目 ID: P2234

难度:普及+/提高

标签:树状数组离散化前驱后继python

日期: 2026-07-16 18:10

题意

第一天波动值为当天营业额。此后每天的波动值是它与此前任意一天营业额的最小绝对差,求所有波动值之和。

思路

在一组有序数中,离新值最近的旧值只可能是它的前驱或后继。问题变成动态维护已出现营业额,并查询前后两个值。

Python 标准库没有平衡树。因为所有营业额已在输入中,可以先 sorted(set(values)) 离散化,再用 Fenwick 树记录哪些不同值已经出现。

对一个首次出现的值:

  1. 前缀和得到比它小的已出现值数量 less
  2. Fenwick 的 kth(less) 找前驱,kth(less+1) 找后继;
  3. 取两个差值的较小者;
  4. 把当前排名加入树。

重复营业额的波动为零,不必重复加入只记录“是否出现”的树。

Python 知识

  • sorted(set(values)) 同时完成去重和排序。
  • 字典推导式建立“原值到离散排名”的映射。
  • seen 集合用平均 O(1)O(1) 判断重复值。
  • index & -index 是 Fenwick 树的 lowbit;kth 用二进制倍增寻找第 k 个已出现排名。
  • /home/rainboy/mycode/hugo-blog/content/program_language/python/sorting_and_ordering.md:排序与规范化。
  • /home/rainboy/mycode/hugo-blog/content/program_language/python/collections_toolkit.md:集合与字典。

代码

python
import sys


data = list(map(int, sys.stdin.buffer.read().split()))
n = data[0]
values = data[1:1 + n]
ordered = sorted(set(values))
rank = {value: index + 1 for index, value in enumerate(ordered)}
tree = [0] * (len(ordered) + 1)


def add(index):
    while index < len(tree):
        tree[index] += 1
        index += index & -index


def prefix_sum(index):
    total = 0
    while index:
        total += tree[index]
        index -= index & -index
    return total


def kth(order):
    index = 0
    step = 1 << (len(tree).bit_length() - 1)
    while step:
        nxt = index + step
        if nxt < len(tree) and tree[nxt] < order:
            index = nxt
            order -= tree[nxt]
        step >>= 1
    return index + 1


answer = values[0]
seen = {values[0]}
add(rank[values[0]])

for value in values[1:]:
    if value in seen:
        continue
    index = rank[value]
    less = prefix_sum(index - 1)
    total = len(seen)
    differences = []
    if less:
        differences.append(value - ordered[kth(less) - 1])
    if less < total:
        differences.append(ordered[kth(less + 1) - 1] - value)
    answer += min(differences)
    seen.add(value)
    add(index)

print(answer)
cpp
/**
 * P2234 [HNOI2002] 营业额统计
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-07-27 00:00
 * update_at: 2026-07-27 00:00
 */

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

const int MAXN = 33000;
int a[MAXN];  // 存所有营业额
int n;

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);

    // 第一天波动值就是当天营业额本身
    long long ans = a[1];
    // 对每天 i,在前 i-1 天中找与 a[i] 差值最小的那天
    for (int i = 2; i <= n; ++i) {
        int min_diff = abs(a[i] - a[1]);
        for (int j = 2; j < i; ++j) {
            int diff = abs(a[i] - a[j]);
            if (diff < min_diff) min_diff = diff;
        }
        ans += min_diff;
    }
    printf("%lld\n", ans);
    return 0;
}

复杂度

离散化为 O(nlogn)O(n\log n),每个不同值查询和插入为 O(logn)O(\log n);总时间 O(nlogn)O(n\log n),空间 O(n)O(n)

总结

动态最近值只需前驱和后继。缺少标准平衡树时,“已知全部值 + 坐标压缩 + Fenwick 第 k 小”是可靠的 Python 替代方案。