slope trick
比较优秀的一道 slope trick 题目。是一个练习带权 slope trick 的题,并不需要手写数据结构加速。
Rosabel 大佬的线段树解法太长了,像我这样的刚学 OI 的萌新完全写不了,这里介绍一种很玄学的 std::map 维护的方法。
我的方法大部分来自官方题解,一部分来自 Rosabel 大佬的题解,如果部分推理过程与官方题解雷同,属正常现象。
不会维护拐点的 slope trick 的人看起来可能比较费劲。
开始讲解
先不思考如何记录过程,下面先说一下转移方法。
考虑朴素 DP,定义
转移为
下面开始考虑如何维护。
这是一个基础的带权 slope trick 题目,后面讲得会很慢,
两步分开处理,我们用经典的 slope trick 维护方法,左右各开一个数据结构维护拐点信息,平常我们都是使用 std::priority_queue 或者 std::multiset 维护,但是这一题中拐点斜率变化量可以达到 std::map。(我是不会告诉你我不会自己写数据结构的)
我们用两个 std::map 维护拐点,斜率为
上面的过程中,第一步形如让斜率小于 std::map 上的维护就是不断删或减小每一侧的 std::map 的 Value,直到每一侧的 Value 和均为
第二步形如插入一个绝对值的倍数,这是维护拐点的 slope trick 很 static 的操作,将这玩意分为
因此我们就可以写出一个最基础的代码了。
#include <bits/stdc++.h>
using namespace std;
#define int long long
int n, c, d, Lcnt, Rcnt, x[1000005], minf;
map<int, int> L, R;
signed main() {
cin >> n >> c >> d;
L[0] = R[0] = c;
Lcnt = Rcnt = c;
for (int i = 1; i <= n; i++) {
cin >> x[i];
while (Lcnt > c) { // 削掉 < -c 的斜率
if (L.begin()->second > Lcnt - c) {
L.begin()->second -= Lcnt - c;
Lcnt = c;
} else {
Lcnt -= L.begin()->second;
L.erase(L.begin());
}
}
while (Rcnt > c) { // 削掉 > c 的斜率
if ((--R.end())->second > Rcnt - c) {
(--R.end())->second -= Rcnt - c;
Rcnt = c;
} else {
Rcnt -= (--R.end())->second;
R.erase(--R.end());
}
}
int add_k = d;
if (R.begin()->first >= x[i]) { // 插入 (j-x[i])+
L[x[i]] += add_k;
} else {
int td = add_k;
while (td) {
if (R.begin()->second > td) { // 当前的 min{R}.Value 已经够大了,將剩下的 td 个 (j-x[i])+ 全部插入,minf 还可以在 min{R} 取值
minf += max(0ll, x[i] - R.begin()->first) * td;
L[R.begin()->first] += td;
R.begin()->second -= td;
R[x[i]] += td;
td = 0;
} else {
int num = R.begin()->second; // 当前的 min{R}.Value 不够大,先放进去 min{R}.Value 个 (j-x[i])+,minf 可以在 min{R} 取值
minf += max(0ll, x[i] - R.begin()->first) * num;
td -= num;
L[R.begin()->first] += num; // 随后是维护拐点的 slope trick 的标准代码
R.erase(R.begin());
R[x[i]] += num; // 这次只放 min{R}.Value 个,并让新的新的 minf 可以在新的 min{R}.Value 上取到
}
}
}
if ((--L.end())->first <= x[i]) { // 插入 (x[i]-j)+,同理
R[x[i]] += add_k;
} else {
int td = add_k;
while (td) {
map<int, int>::iterator it = --L.end();
if (it->second > td) {
minf += max(0ll, it->first - x[i]) * td;
R[it->first] += td;
it->second -= td;
L[x[i]] += td;
td = 0;
} else {
int num = it->second;
minf += max(0ll, it->first - x[i]) * num;
td -= num;
R[it->first] += num;
L.erase(it);
L[x[i]] += num;
}
}
}
Lcnt += add_k; // 维护一下 L 和 R 的和
Rcnt += add_k;
}
cout << minf << endl;
return 0;
}
但是这个会 TLE 九个点,为什么?因为复杂度不正确,如果这个
怎么优化?我们可以只插入
我们将代码中的 add_k 设为
#include <bits/stdc++.h>
using namespace std;
#define int long long
int n, c, d, Lcnt, Rcnt, x[1000005], minf;
map<int, int> L, R;
signed main() {
cin >> n >> c >> d;
L[0] = R[0] = c;
Lcnt = Rcnt = c;
for (int i = 1; i <= n; i++) {
cin >> x[i];
while (Lcnt > c) { // 削掉 < -c 的斜率
if (L.begin()->second > Lcnt - c) {
L.begin()->second -= Lcnt - c;
Lcnt = c;
} else {
Lcnt -= L.begin()->second;
L.erase(L.begin());
}
}
while (Rcnt > c) { // 削掉 > c 的斜率
if ((--R.end())->second > Rcnt - c) {
(--R.end())->second -= Rcnt - c;
Rcnt = c;
} else {
Rcnt -= (--R.end())->second;
R.erase(--R.end());
}
}
int add_k = min(d, 2 * c);
if (R.begin()->first >= x[i]) { // 插入 (j-x[i])+
L[x[i]] += add_k;
} else {
int td = add_k;
while (td) {
if (R.begin()->second > td) { // 当前的 min{R}.Value 已经够大了,將剩下的 td 个 (j-x[i])+ 全部插入,minf 还可以在 min{R} 取值
minf += max(0ll, x[i] - R.begin()->first) * td;
L[R.begin()->first] += td;
R.begin()->second -= td;
R[x[i]] += td;
td = 0;
} else {
int num = R.begin()->second; // 当前的 min{R}.Value 不够大,先放进去 min{R}.Value 个 (j-x[i])+,minf 可以在 min{R} 取值
minf += max(0ll, x[i] - R.begin()->first) * num;
td -= num;
L[R.begin()->first] += num; // 随后是维护拐点的 slope trick 的标准代码
R.erase(R.begin());
R[x[i]] += num; // 这次只放 min{R}.Value 个,并让新的新的 minf 可以在新的 min{R}.Value 上取到
}
}
}
if ((--L.end())->first <= x[i]) { // 插入 (x[i]-j)+,同理
R[x[i]] += add_k;
} else {
int td = add_k;
while (td) {
map<int, int>::iterator it = --L.end();
if (it->second > td) {
minf += max(0ll, it->first - x[i]) * td;
R[it->first] += td;
it->second -= td;
L[x[i]] += td;
td = 0;
} else {
int num = it->second;
minf += max(0ll, it->first - x[i]) * num;
td -= num;
R[it->first] += num;
L.erase(it);
L[x[i]] += num;
}
}
}
Lcnt += add_k; // 维护一下 L 和 R 的和
Rcnt += add_k;
}
cout << minf << endl;
return 0;
}
先别走,我们还没有记录方案,记录方案的方法很简单,我们每一次砍完
输出时,我们知道
完整代码,91 行,2KB 出头:
#include <bits/stdc++.h>
using namespace std;
#define int long long
int n, c, d, Lcnt, Rcnt, current_min[1000005], current_max[1000005], x[1000005], minf;
map<int, int> L, R;
signed main() {
cin >> n >> c >> d;
L[0] = R[0] = c;
Lcnt = Rcnt = c;
for (int i = 1; i <= n; i++) {
cin >> x[i];
while (Lcnt > c) { // 削掉 < -c 的斜率
if (L.begin()->second > Lcnt - c) {
L.begin()->second -= Lcnt - c;
Lcnt = c;
} else {
Lcnt -= L.begin()->second;
L.erase(L.begin());
}
}
while (Rcnt > c) { // 削掉 > c 的斜率
if ((--R.end())->second > Rcnt - c) {
(--R.end())->second -= Rcnt - c;
Rcnt = c;
} else {
Rcnt -= (--R.end())->second;
R.erase(--R.end());
}
}
current_min[i] = L.begin()->first;
current_max[i] = (--R.end())->first;
int add_k = min(d, 2 * c);
if (R.begin()->first >= x[i]) { // 插入 (j-x[i])+
L[x[i]] += add_k;
} else {
int td = add_k;
while (td) {
if (R.begin()->second > td) { // 当前的 min{R}.Value 已经够大了,將剩下的 td 个 (j-x[i])+ 全部插入,minf 还可以在 min{R} 取值
minf += max(0ll, x[i] - R.begin()->first) * td;
L[R.begin()->first] += td;
R.begin()->second -= td;
R[x[i]] += td;
td = 0;
} else {
int num = R.begin()->second; // 当前的 min{R}.Value 不够大,先放进去 min{R}.Value 个 (j-x[i])+,minf 可以在 min{R} 取值
minf += max(0ll, x[i] - R.begin()->first) * num;
td -= num;
L[R.begin()->first] += num; // 随后是维护拐点的 slope trick 的标准代码
R.erase(R.begin());
R[x[i]] += num; // 这次只放 min{R}.Value 个,并让新的新的 minf 可以在新的 min{R}.Value 上取到
}
}
}
if ((--L.end())->first <= x[i]) { // 插入 (x[i]-j)+,同理
R[x[i]] += add_k;
} else {
int td = add_k;
while (td) {
map<int, int>::iterator it = --L.end();
if (it->second > td) {
minf += max(0ll, it->first - x[i]) * td;
R[it->first] += td;
it->second -= td;
L[x[i]] += td;
td = 0;
} else {
int num = it->second;
minf += max(0ll, it->first - x[i]) * num;
td -= num;
R[it->first] += num;
L.erase(it);
L[x[i]] += num;
}
}
}
Lcnt += add_k; // 维护一下 L 和 R 的和
Rcnt += add_k;
}
cout << minf << endl;
int ans_pos = R.begin()->first;
vector<int> ans_poss;
for (int i = n; i >= 1; i--) {
ans_poss.push_back(ans_pos);
ans_pos = max(min(current_max[i], ans_pos), current_min[i]);
}
reverse(ans_poss.begin(), ans_poss.end());
for (int i : ans_poss) {
cout << i << " ";
}
return 0;
}