[USACO04OPEN] MooFest G
按 v 排序消掉 max,每头牛只与前面牛配对,两个树状数组维护坐标数量与坐标和。
OJ: luogu
题目 ID: P2345
难度:普及+/提高
标签:树状数组排序前缀和
日期: 2026-08-05 14:35
题意
数据范围:
思路
一句话本质:音量公式里的
先看最直接的暴力:
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-08-05 14:30
* update_at: 2026-08-05 14:30
*/
// brute.cpp:小数据暴力解,O(n^2) 枚举所有点对按公式直接计算。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 20005;
int n;
long long v[MAXN], x[MAXN];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++) cin >> v[i] >> x[i];
long long ans = 0;
for (int i = 1; i <= n; i++)
for (int j = i + 1; j <= n; j++) {
long long dist = x[i] - x[j];
if (dist < 0) dist = -dist;
ans += max(v[i], v[j]) * dist;
}
cout << ans << '\n';
return 0;
}暴力枚举所有
如果让听力小的先被处理,那它和听力大的牛配对时,
剩下的
对第
绝对值拆成左右两边:坐标比
为什么用两个树状数组?
坐标范围只有
bit_cnt维护坐标出现次数(查); bit_sum维护坐标值之和(查)。
查询
long long cnt_less = query(bit_cnt, x - 1);
long long sum_less = query(bit_sum, x - 1);
long long cnt_greater = cnt_all - cnt_less; // cnt_all = i-1
long long sum_greater = sum_all - sum_less;
ans += 1LL * v * (x * cnt_less - sum_less + sum_greater - x * cnt_greater);处理完当前牛再把它插入两个 BIT,保证"前面"的含义正确。
代码
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-08-05 14:30
* update_at: 2026-08-05 14:30
*/
// 按 v 排序后,处理第 i 头牛时它和之前牛的贡献 max(v) 就是 v[i],
// 用两个树状数组维护前面牛的坐标数量与坐标和,O(n log n) 统计。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 20005;
int n;
struct Cow {
int v, x;
};
Cow cows[MAXN];
long long bit_cnt[MAXN]; // 坐标出现次数
long long bit_sum[MAXN]; // 坐标值之和
bool cmp_v(const Cow& a, const Cow& b) {
return a.v < b.v;
}
void add(long long* bit, int idx, long long val) {
for (; idx <= MAXN; idx += idx & -idx) bit[idx] += val;
}
long long query(long long* bit, int idx) {
long long res = 0;
for (; idx > 0; idx -= idx & -idx) res += bit[idx];
return res;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++) cin >> cows[i].v >> cows[i].x;
sort(cows + 1, cows + n + 1, cmp_v); // 按听力升序
long long ans = 0;
for (int i = 1; i <= n; i++) {
int x = cows[i].x;
int v = cows[i].v;
// 前面(v 更小)的牛中,x 小于当前坐标的数量与坐标和
long long cnt_less = query(bit_cnt, x - 1);
long long sum_less = query(bit_sum, x - 1);
// 全部前面牛的数量与坐标和
long long cnt_all = i - 1;
long long sum_all = query(bit_sum, MAXN);
// 剩下的就是 x 大于当前坐标的部分
long long cnt_greater = cnt_all - cnt_less;
long long sum_greater = sum_all - sum_less;
// 贡献 = v * (x*数量 - 坐标和 + 坐标和 - x*数量),绝对值拆成左右两边
ans += 1LL * v * (x * cnt_less - sum_less + sum_greater - x * cnt_greater);
// 把当前牛插入 BIT
add(bit_cnt, x, 1);
add(bit_sum, x, x);
}
cout << ans << '\n';
return 0;
}复杂度
排序
总结
这题的核心套路是按排序消掉
图示解析
这张图展示按 v 排序后,处理第 4 头牛时前面牛的统计:
按 v 排序后的牛: (v,x)
(2,6) (2,5) (3,1) (4,3)
↑ ↑ ↑ ↑
已插入 BIT 当前牛 v=4, x=3
前面牛中:x < 3 的有 (3,1) → cnt_less=1, sum_less=1
x > 3 的有 (2,6),(2,5) → cnt_greater=2, sum_greater=11
贡献 = 4 × [(3×1-1) + (11-3×2)] = 4 × (2+5) = 28读图方法:每头牛只和前面(v 更小)的牛配对,