排序后维护基础贡献,单次查询只计算删除旧值再插入新值造成的区间位移贡献。
OJ: usaco
题目 ID: 1326
难度:普及+/提高
标签:排序前缀和二分usaco
日期: 2026-07-11 19:00
题意
有
如果第
现在有
思路
先看一个小数据暴力。每次询问直接修改一个值,重新排序,再计算
cpp
/**
* 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-07-11 19:00
* update_at: 2026-07-11 19:02
*/
// brute.cpp:小数据暴力解,用来帮助理解题意并辅助对拍。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 205;
int n, q;
ll a[MAXN];
ll b[MAXN];
ll calc_answer() {
for (int i = 1; i <= n; i++) {
b[i] = a[i];
}
sort(b + 1, b + n + 1);
ll ans = 0;
for (int i = 1; i <= n; i++) {
ans += (ll)i * b[i];
}
return ans;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
cin >> q;
while (q--) {
int idx;
ll val;
cin >> idx >> val;
ll old = a[idx];
a[idx] = val;
cout << calc_answer() << '\n';
a[idx] = old;
}
return 0;
}暴力每次都排序,复杂度太高。满分做法只分析“一个元素移动”带来的贡献变化。
先把原数组排序成 b[1..n],基础答案是:
同时记录原来的第 idx 个数在排序数组中的位置 old_pos。
一次询问把旧值 old_val 改成 val,等价于:
- 从排序数组中删除
old_pos位置的旧值; - 把新值
val插入到它应该在的新位置new_pos。
new_pos 可以用 lower_bound 找到。需要注意:如果 val > old_val,旧值被删除后,新值的插入位置要向左修正一格。
删除旧值后,如果
text
old_pos + 1 ... new_pos这些数的系数都减少 1,所以总贡献减少这一段的元素和。
如果 new_pos < old_pos,说明左侧的一段数会整体右移一格:
text
new_pos ... old_pos - 1这些数的系数都增加 1,所以总贡献增加这一段的元素和。
区间和用排序数组的前缀和 prefix_sum[] 在
代码
cpp
/**
* 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-07-11 19:00
* update_at: 2026-07-11 19:02
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 150005;
int n, q;
ll a[MAXN]; // 原数组,按输入下标保存
ll b[MAXN]; // 排序后的数组
ll prefix_sum[MAXN];
ll base_answer;
int ord[MAXN]; // 原下标的排序顺序
int pos[MAXN]; // pos[i] 表示原第 i 个数在排序数组中的位置
bool cmp_ord(int x, int y) {
if (a[x] != a[y]) return a[x] < a[y];
return x < y;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
b[i] = a[i];
ord[i] = i;
}
sort(ord + 1, ord + n + 1, cmp_ord);
for (int i = 1; i <= n; i++) {
pos[ord[i]] = i;
}
sort(b + 1, b + n + 1);
for (int i = 1; i <= n; i++) {
prefix_sum[i] = prefix_sum[i - 1] + b[i];
base_answer += (ll)i * b[i];
}
cin >> q;
while (q--) {
int idx;
ll val;
cin >> idx >> val;
int old_pos = pos[idx];
ll old_val = b[old_pos];
int new_pos = lower_bound(b + 1, b + n + 1, val) - b;
if (val > old_val) {
new_pos--;
}
ll ans = base_answer;
ans -= (ll)old_pos * old_val;
if (new_pos >= old_pos) {
// old_pos+1..new_pos 整体向左移动一格,贡献各减去自身值。
ans -= prefix_sum[new_pos] - prefix_sum[old_pos];
} else {
// new_pos..old_pos-1 整体向右移动一格,贡献各增加自身值。
ans += prefix_sum[old_pos - 1] - prefix_sum[new_pos - 1];
}
ans += (ll)new_pos * val;
cout << ans << '\n';
}
return 0;
}复杂度
预处理排序和前缀和需要
每个询问一次二分,时间复杂度
总时间复杂度为
总结
本题的核心是先把最优顺序转化为排序后的贡献公式。
每个查询只改变一个元素,因此不需要重新排序整张表,只要计算删除旧位置、插入新位置时中间区间的整体位移贡献即可。