反悔贪心
反悔贪心
一般贪心仅能解出局部最优解,而一部分题目不满足局部最优解等价于全局最优解,于是引入反悔操作。
本质思想是每次都贪心进行操作,若以后有更优情况则反悔最劣的操作,即跑一遍局部最优假贪心,再通过反悔操作得到答案。
不难发现反悔贪心的核心在于维护反悔操作所需的值,所以当有些题目可以很方便地维护出操作贡献时,可以考虑反悔贪心。
P2949 [USACO09OPEN] Work Scheduling G
有 \(n\) 项任务,每一时刻可以完成一项任务,第 \(i\) 项任务的截止时间为 \(d_i\) ,价值为 \(p_i\) ,求最大价值。
\(n \leq 10^5\)
首先按截止时间排序,依次决策每个任务:
- 若当前任务可以做就先去做。
- 否则若当前任务价值高于做过的最小价值的任务,则将其替换为该任务。
时间复杂度 \(O(n \log n)\) 。
类似的题目:P4053 [JSOI2007] 建筑抢修
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 1e5 + 7;
pair<int, int> a[N];
int n;
signed main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
scanf("%d%d", &a[i].first, &a[i].second);
sort(a + 1, a + 1 + n);
priority_queue<int, vector<int>, greater<int> > q;
for (int i = 1; i <= n; ++i) {
if (q.size() < a[i].first)
q.emplace(a[i].second);
else if (a[i].second > q.top())
q.pop(), q.emplace(a[i].second);
}
ll ans = 0;
while (!q.empty())
ans += q.top(), q.pop();
printf("%lld", ans);
return 0;
}
CF865D Buy Low Sell High
已知接下来 \(n\) 天的股票价格,每一天可以选择买进、卖出或不做,求 \(n\) 天后的最大利润。
\(n \leq 3 \times 10^5\)
考虑如何反悔形如“第 \(i\) 天买入,第 \(j\) 天卖出”的决策,决策的同时引入一个价格为 \(a_j\) 的物品,这样“第 \(i\) 天买入,第 \(j\) 天卖出,同时买入价格为 \(a_j\) 的物品,并在第 \(k\) 天卖出“就等价于“第 \(i\) 天买入,第 \(k\) 天卖出”,实现了反悔操作。
维护一个堆代表可选物品价格,从前向后遍历每一天。对于第 \(i\) 天,找到堆中最小价格 \(a_j\) 并加入 \(a_i\) 代表 \(a_i\) 这一天可选。若 \(a_i > a_j\) ,则把答案加上 \(a_i - a_j\) ,并向集合中再次加入 \(a_i\) ,并删除 \(a_j\) 。
时间复杂度 \(O(n \log n)\) 。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
int n;
signed main() {
scanf("%d", &n);
priority_queue<int, vector<int>, greater<int> > q;
ll ans = 0;
for (int i = 1; i <= n; ++i) {
int x;
scanf("%d", &x);
if (!q.empty() && q.top() < x)
ans += x - q.top(), q.pop(), q.emplace(x);
q.emplace(x);
}
printf("%lld", ans);
return 0;
}
P3545 [POI2012] HUR-Warehouse Store
有 \(n\) 天,第 \(i\) 天上午会进货 \(a_i\) 件商品,中午会有顾客购买 \(b_i\) 件商品,可选择满足或无视。求最多能够满足多少顾客的需求。
\(n \leq 2.5 \times 10^5\)
每次能选就选,不能选时尝试替换掉原来 \(b_i\) 最大的顾客(这样可以剩下更多商品给后面的顾客),时间复杂度 \(O(n \log n)\) 。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 2.5e5 + 7;
int a[N], b[N];
bool choose[N];
int n;
signed main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
scanf("%d", a + i);
for (int i = 1; i <= n; ++i)
scanf("%d", b + i);
priority_queue<pair<int, int> > q;
ll surplus = 0;
for (int i = 1; i <= n; ++i) {
surplus += a[i];
if (surplus >= b[i])
surplus -= b[i], q.emplace(b[i], i);
else if (!q.empty() && b[i] < q.top().first)
surplus += q.top().first - b[i], q.pop(), q.emplace(b[i], i);
}
printf("%d\n", (int)q.size());
while (!q.empty())
choose[q.top().second] = true, q.pop();
for (int i = 1; i <= n; ++i)
if (choose[i])
printf("%d ", i);
return 0;
}
P1484 种树
求 \(n\) 个数中选出至多 \(k\) 个两两不相邻的数的最大和。
\(n \leq 3 \times 10^5\)
设当前选出的最大的点为 \(id\) ,它左右两边的点分别是 \(x, y\) ,就删掉这三个点并新建一个点权为 \(a_x + a_y - a_{id}\) 的,这样下次选出这个点 \(p\) 时就实现了反悔操作。同时因为反悔操作舍弃了 \(id\) ,所以之后也不会选 \(id\) ,故正确。用链表维护相邻点即可做到 \(O(n \log n)\) 。
类似的题目:
- P1792 [国家集训队] 种树 :推广到环上,注意这题是选恰好 \(m\) 个。
- P3620 [APIO/CTSC2007] 数据备份 :贪心可以感受到二元组一定相邻,将相邻两个数建点则转化为这题。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 5e5 + 7;
struct List {
ll val;
int pre, nxt;
} l[N];
bool vis[N];
int n, k;
signed main() {
scanf("%d%d", &n, &k);
priority_queue<pair<ll, int> > q;
for (int i = 1; i <= n; ++i) {
ll x;
scanf("%lld", &x);
l[i] = (List){x, i - 1, i + 1}, q.emplace(x, i);
}
ll ans = 0;
while (k--) {
while (vis[q.top().second])
q.pop();
int val = q.top().first, x = q.top().second;
q.pop();
if (val <= 0)
break;
ans += val, vis[l[x].pre] = vis[l[x].nxt] = true;
l[x].val = l[l[x].pre].val + l[l[x].nxt].val - l[x].val;
l[x].pre = l[l[x].pre].pre, l[x].nxt = l[l[x].nxt].nxt;
l[l[x].pre].nxt = l[l[x].nxt].pre = x;
q.emplace(l[x].val, x);
}
printf("%lld", ans);
return 0;
}
CF436E Cardboard Box
有 \(n\) 个关卡,每个关卡可以花 \(a_i\) 代价得到一颗星,也可以花 \(b_i\) 代价得到两颗星,也可以不玩。求获得 \(w\) 颗星最少代价。
\(n \leq 3 \times 10^5\)
考虑如何从选了 \(i\) 颗星的方案扩展得到选 \(i + 1\) 颗星的方案:
- 选一个没有选星星的位置 \(i\) ,付出 \(a_i\) 的代价选一颗星。
- 选一个已选一颗星的位置 \(i\) ,付出 \(b_i - a_i\) 的代价选两颗星。
- 选一个已选一颗星的位置 \(i\) ,再选一个没有选星星的位置 \(j\) ,将原来 \(i\) 位置上的星星反悔不选,再在 \(j\) 位置上选两颗星,代价为 $b_j - a_i $。
- 选一个已选两颗星的位置 \(i\) ,再选一个没有选星星的位置 \(j\) ,将原来 \(i\) 位置上的星星反悔选一颗星星,再在 \(j\) 位置上选两颗星,代价为 \(b_j - (b_i - a_i)\) 。
用堆维护即可做到 \(O(n \log n)\) 。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const ll inf = 1e18;
const int N = 3e5 + 7;
ll a[N], b[N];
int tag[N];
int n, w;
struct Heap {
priority_queue<pair<ll, int>, vector<pair<ll, int> >, greater<pair<ll, int> > > q;
int op;
inline Heap(int _op) : op(_op) {}
inline void maintain() {
while (!q.empty() && tag[q.top().second] != op)
q.pop();
}
inline bool empty() {
return maintain(), q.empty();
}
inline pair<ll, int> top() {
return maintain(), q.top();
}
inline void emplace(pair<ll, int> x) {
q.emplace(x);
}
} q1(0), q2(1), q3(1), q4(0), q5(2);
inline void update0(int x) {
tag[x] = 0;
q1.q.emplace(a[x], x);
q4.q.emplace(b[x], x);
}
inline void update1(int x) {
tag[x] = 1;
q2.q.emplace(b[x] - a[x], x);
q3.q.emplace(-a[x], x);
}
inline void update2(int x) {
tag[x] = 2;
q5.q.emplace(-(b[x] - a[x]), x);
}
signed main() {
scanf("%d%d", &n, &w);
for (int i = 1; i <= n; ++i) {
scanf("%d%d", a + i, b + i);
q1.q.emplace(a[i], i), q4.q.emplace(b[i], i);
}
ll ans = 0;
while (w--) {
ll res = inf;
int op = 0, x, y;
if (!q1.empty() && q1.top().first < res)
res = q1.top().first, x = q1.top().second, op = 1;
if (!q2.empty() && q2.top().first < res)
res = q2.top().first, x = q2.top().second, op = 2;
if (!q3.empty() && !q4.empty() && q3.top().first + q4.top().first < res)
res = q3.top().first + q4.top().first, x = q3.top().second, y = q4.top().second, op = 3;
if (!q5.empty() && !q4.empty() && q5.top().first + q4.top().first < res)
res = q5.top().first + q4.top().first, x = q5.top().second, y = q4.top().second, op = 4;
ans += res;
if (op == 1)
update1(x);
else if (op == 2)
update2(x);
else if (op == 3)
update0(x), update2(y);
else
update1(x), update2(y);
}
printf("%lld\n", ans);
for (int i = 1; i <= n; ++i)
printf("%d", tag[i]);
return 0;
}
P3045 [USACO12FEB] Cow Coupons G
有 \(n\) 头奶牛,第 \(i\) 头初始价格为 \(p_i\) ,使用优惠券时价格为 \(c_i\) 。你有 \(k\) 张优惠券和 \(m\) 元,求购买奶牛的最大数量。
\(n \leq 5 \times 10^4\)
能够使买入奶牛数量增加的三种操作:
- 用优惠券买一头未买的奶牛。
- 不用优惠券买一头未买的奶牛。
- 将一头用优惠券买的奶牛替换为不用优惠券买的同一奶牛,并用优惠券买入一头未买的奶牛。
用堆维护即可做到 \(O(n \log n)\) 。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const ll inf = 1e18;
const int N = 5e4 + 7;
int id[N];
struct Heap {
priority_queue<pair<int, int> > q;
int op;
inline Heap(int _op) : op(_op) {}
inline void maintain() {
while (!q.empty() && id[q.top().second] != op)
q.pop();
}
inline bool empty() {
return maintain(), q.empty();
}
inline pair<int, int> top() {
return maintain(), q.top();
}
} q1(0), q2(0), q3(1);
int p[N], c[N];
ll m;
int n, k;
inline void update0(int x) {
id[x] = 0;
q1.q.emplace(-p[x], x), q2.q.emplace(-c[x], x);
}
inline void update1(int x) {
id[x] = 1;
q3.q.emplace(c[x] - p[x], x);
}
inline void update2(int x) {
id[x] = 2;
}
signed main() {
scanf("%d%d%lld", &n, &k, &m);
for (int i = 1; i <= n; ++i)
scanf("%d%d", p + i, c + i), update0(i);
int ans = 0;
for (;;) {
ll res = inf, res1 = inf, res2 = inf, res3 = inf;
if (!q1.empty())
res = min(res, res1 = -q1.top().first); // add p[x]
if (k && !q2.empty())
res = min(res, res2 = -q2.top().first); // add c[x]
if (!q3.empty() && !q2.empty())
res = min(res, res3 = -q3.top().first - q2.top().first); // c[i] -> p[i] + c[j]
if (res > m)
break;
m -= res;
if (res1 == res)
update2(q1.top().second), ++ans;
else if (res2 == res)
update1(q2.top().second), ++ans, --k;
else
update2(q3.top().second), update1(q2.top().second), ++ans;
}
printf("%d", ans);
return 0;
}
P5470 [NOI2019] 序列
给定 \(a_{1 \sim n}, b_{1 \sim n}\) ,需要分别对两个序列各指定恰好 \(K\) 个下标,要求至少有 \(L\) 个下标在两个序列中都被指定,最大化这 \(2K\) 个下标在序列中对应的元素的总和。
\(\sum n \leq 10^6\)
首先考虑没有 \(L\) 限制的贪心,从 \(a, b\) 中各选 \(K\) 个最大的即可。
对于 \(L\) 的限制,考虑反悔贪心,每次选取增量尽可能大的操作反悔,使得每次返回后都能使当前同时出现在两个序列中的点数多一。
- 找一个选了的 \(a_i\) 和一个选了的 \(b_j\) ,用 \(b_i\) 代替 \(b_j\) ,增量为 \(b_i - b_j\) 。
- 找一个选了的 \(a_i\) 和一个选了的 \(b_j\) ,用 \(a_j\) 代替 \(a_i\) ,增量为 \(a_j - a_i\) 。
- 找一个选了的 \(a_i, b_j\) ,再找一个没选的位置 \(k\) ,选 \(a_k, b_k\) 代替 \(a_i, b_j\) ,增量为 \(a_k + b_k - a_i - b_j\) 。
- 找一个选了的 \(a_i, b_j\) ,再找一个都选的位置 \(k\) ,选 \(a_j, b_i\) 代替 \(a_k, b_k\) ,增量是 \(a_j + b_i - a_k - b_k\) 。
时间复杂度 \(O(n \log n)\) 。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const ll inf = 1e18;
const int N = 2e5 + 7;
ll a[N], b[N];
int tag[N];
int n, k, l;
struct Heap {
priority_queue<pair<ll, int> > q;
int op, sign;
inline Heap(int _op, int _sign) : op(_op), sign(_sign) {}
inline void update() {
while (!q.empty() && tag[q.top().second] != op)
q.pop();
}
inline ll topval() {
return update(), (q.empty() ? -inf : q.top().first) * sign;
}
inline int topid() {
return update(), q.top().second;
}
inline void pop() {
q.pop();
}
inline void clear() {
while (!q.empty())
q.pop();
}
inline void emplace(ll val, int id) {
q.emplace(val * sign, id);
}
} q1(1, 1), q2(2, -1), q3(2, 1), q4(1, -1), q5(0, 1), q6(3, -1);
// q1 : 选了 a[i] 后最大的 b[i]
// q2 : 选了 b[i] 后最小的 b[i]
// q3 : 选了 b[i] 后最大的 a[i]
// q4 : 选了 a[i] 后最小的 a[i]
// q5 : 一个没选最大的 a[i] + b[i]
// q6 : 两个都选的最小的 a[i] + b[i]
priority_queue<pair<ll, int> > qa, qb;
inline void update0(int x) {
tag[x] = 0;
q5.emplace(a[x] + b[x], x);
}
inline void update1(int x) {
tag[x] = 1;
q1.emplace(b[x], x), q4.emplace(a[x], x);
}
inline void update2(int x) {
tag[x] = 2;
q2.emplace(b[x], x), q3.emplace(a[x], x);
}
inline void update3(int x) {
tag[x] = 3;
q6.emplace(a[x] + b[x], x);
}
signed main() {
int T;
scanf("%d", &T);
while (T--) {
scanf("%d%d%d", &n, &k, &l);
while (!qa.empty())
qa.pop();
while (!qb.empty())
qb.pop();
for (int i = 1; i <= n; ++i)
scanf("%d", a + i), qa.emplace(a[i], i);
for (int i = 1; i <= n; ++i)
scanf("%d", b + i), qb.emplace(b[i], i);
memset(tag + 1, 0, sizeof(int) * n);
ll ans = 0;
while (k--) {
ans += qa.top().first, tag[qa.top().second] |= 1, qa.pop();
ans += qb.top().first, tag[qb.top().second] |= 2, qb.pop();
}
q1.clear(), q2.clear(), q3.clear(), q4.clear(), q5.clear(), q6.clear();
for (int i = 1; i <= n; ++i) {
if (!tag[i])
update0(i);
else if (tag[i] == 1)
update1(i);
else if (tag[i] == 2)
update2(i);
else
update3(i), --l;
}
if (l <= 0) {
printf("%lld\n", ans);
continue;
}
while (l--) {
ll res = -inf;
int op = -1;
if (q1.topval() - q2.topval() > res)
res = q1.topval() - q2.topval(), op = 1;
if (q3.topval() - q4.topval() > res)
res = q3.topval() - q4.topval(), op = 2;
if (q5.topval() - q2.topval() - q4.topval() > res)
res = q5.topval() - q2.topval() - q4.topval(), op = 3;
if (q1.topval() + q3.topval() - q6.topval() > res)
res = q1.topval() + q3.topval() - q6.topval(), op = 4;
ans += res;
if (op == 1) {
int i = q1.topid(), j = q2.topid();
q1.pop(), q2.pop();
update3(i), update0(j);
} else if (op == 2) {
int i = q3.topid(), j = q4.topid();
q3.pop(), q4.pop();
update3(i), update0(j);
} else if (op == 3) {
int k = q5.topid(), i = q2.topid(), j = q4.topid();
q5.pop(), q2.pop(), q4.pop();
update3(k), update0(i), update0(j);
} else if (op == 4) {
int i = q1.topid(), j = q3.topid(), k = q6.topid();
q1.pop(), q3.pop(), q6.pop();
update3(i), update3(j), update0(k);
}
}
printf("%lld\n", ans);
}
return 0;
}
[AGC018C] Coins
有 \(x + y + z\) 个人,第 \(i\) 人有 \(a_i\) 金币、\(b_i\) 银币、\(c_i\) 铜币。要选出 \(x\) 个人获得其金币,\(y\) 个人获得其银币,\(z\) 个人获得其铜币,在选人不重复的情况下求币总数的最大值。
\(x + y + z \leq 10^5\)
考虑先随便找出一种合法的方案,然后反悔操作都是类似于环的重分配操作,一共五种:
- \(A \to B, B \to C, C \to A\) 。
- \(A \to C, C \to B, B \to A\) 。
- \(A \to B, B \to A\) 。
- \(A \to C, C \to A\) 。
- \(B \to C, C \to B\) 。
开六个堆维护每一步转换的决策即可,时间复杂度 \(O(n \log n)\) 。
类似的题目:CF730I Olympiad in Programming and Sports
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const ll inf = 1e18;
const int N = 1e5 + 7;
int a[N], b[N], c[N], tag[N];
int x, y, z;
struct Heap {
priority_queue<pair<ll, int> > q;
int op;
inline Heap(int _op) : op(_op) {}
inline void maintain() {
while (!q.empty() && tag[q.top().second] != op)
q.pop();
}
inline bool empty() {
return maintain(), q.empty();
}
inline pair<ll, int> top() {
return maintain(), q.top();
}
} qxy(1), qxz(1), qyz(2), qyx(2), qzx(3), qzy(3);
inline void updatex(int x) {
tag[x] = 1, qxy.q.emplace(b[x] - a[x], x), qxz.q.emplace(c[x] - a[x], x);
}
inline void updatey(int x) {
tag[x] = 2, qyx.q.emplace(a[x] - b[x], x), qyz.q.emplace(c[x] - b[x], x);
}
inline void updatez(int x) {
tag[x] = 3, qzx.q.emplace(a[x] - c[x], x), qzy.q.emplace(b[x] - c[x], x);
}
signed main() {
scanf("%d%d%d", &x, &y, &z);
for (int i = 1; i <= x + y + z; ++i)
scanf("%d%d%d", a + i, b + i, c + i);
ll ans = 0;
for (int i = 1; i <= x; ++i)
ans += a[i], updatex(i);
for (int i = x + 1; i <= x + y; ++i)
ans += b[i], updatey(i);
for (int i = x + y + 1; i <= x + y + z; ++i)
ans += c[i], updatez(i);
for (;;) {
pair<ll, int> res = make_pair(-inf, 0);
if (!qxy.empty() && !qyx.empty())
res = max(res, make_pair(qxy.top().first + qyx.top().first, 1));
if (!qyz.empty() && !qzy.empty())
res = max(res, make_pair(qyz.top().first + qzy.top().first, 2));
if (!qxz.empty() && !qzx.empty())
res = max(res, make_pair(qxz.top().first + qzx.top().first, 3));
if (!qxy.empty() && !qyz.empty() && !qzx.empty())
res = max(res, make_pair(qxy.top().first + qyz.top().first + qzx.top().first, 4));
if (!qxz.empty() && !qzy.empty() && !qyx.empty())
res = max(res, make_pair(qxz.top().first + qzy.top().first + qyx.top().first, 5));
if (res.first <= 0)
break;
ans += res.first;
if (res.second == 1) {
int x = qxy.top().second, y = qyx.top().second;
updatey(x), updatex(y);
} else if (res.second == 2) {
int y = qyz.top().second, z = qzy.top().second;
updatez(y), updatey(z);
} else if (res.second == 3) {
int x = qxz.top().second, z = qzx.top().second;
updatez(x), updatex(z);
} else if (res.second == 4) {
int x = qxy.top().second, y = qyz.top().second, z = qzx.top().second;
updatey(x), updatez(y), updatex(z);
} else {
int x = qxz.top().second, z = qzy.top().second, y = qyx.top().second;
updatez(x), updatey(z), updatex(y);
}
}
printf("%lld", ans);
return 0;
}
CF802O April Fools' Problem
有 \(n\) 道题,第 \(i\) 天可以花费 \(a_i\) 准备一道题、花费 \(b_i\) 打印一道题。
每天最多准备一道、最多打印一道,准备的题可以留到以后打印。
求准备并打印 \(k\) 道题的最小花费。
\(n \leq 5 \times 10^5\)
先用 wqs 二分去掉恰好 \(k\) 题的限制。考虑在每次打印的时候选取准备花费最少且准备+打印总花费为非正数的题目进行打印,但是这样会使得后面一个打印花费更优的位置无法匹配,于是引入反悔操作。
用一个小根堆维护之前的所有决策,每次取出价格最低的决策,将当前打印的花费加上决策花费后,如果总花费为非正数,则先选择该决策,并将 \(-b_i\) 放入堆中,这样就可以反悔了。时间复杂度 \(O(n \log n \log V)\) 。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 5e5 + 7;
ll a[N], b[N];
int n, k;
inline pair<ll, int> check(ll lambda) {
ll ans = 0;
int cnt = 0;
priority_queue<pair<ll, int> > q;
for (int i = 1; i <= n; ++i)
b[i] -= lambda;
for (int i = 1; i <= n; ++i) {
q.emplace(-a[i], 1);
if (b[i] + -q.top().first < 0) {
ans += b[i] - q.top().first, cnt += q.top().second;
q.pop(), q.emplace(b[i], 0);
}
}
for (int i = 1; i <= n; ++i)
b[i] += lambda;
return make_pair(ans + lambda * cnt, cnt);
}
signed main() {
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; ++i)
scanf("%lld", a + i);
for (int i = 1; i <= n; ++i)
scanf("%lld", b + i);
ll l = -2e9, r = 2e9, lim = -2e9;
while (l <= r) {
ll mid = (l + r) >> 1;
if (check(mid).second >= k)
lim = mid, r = mid - 1;
else
l = mid + 1;
}
printf("%lld", check(lim).first);
return 0;
}
CF280D k-Maximum Subsequence Sum
给出一个长度为 \(n\) 的数列, \(m\) 次操作,每次有两种:
- 修改某个位置的值。
- 询问区间 \([l,r]\) 里选出至多 \(k\) 个不相交的子段和的最大值。
\(n, m \leq 10^5\) ,\(k \leq 20\)
考虑用线段树维护反悔贪心。处理一个区间询问时贪心选最大子段和,再将其全部取相反数,这样下一次算到时就算反悔。
时间复杂度 \(O(nk \log n)\) 。
#include <bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
const int N = 1e5 + 7;
int a[N];
int n, m;
namespace SMT {
struct Node {
int l, r, sum,
lmxsum, lmxpos, lmnsum, lmnpos,
rmxsum, rmxpos, rmnsum, rmnpos,
mxans, lmxanspos, rmxanspos,
mnans, lmnanspos, rmnanspos;
inline Node(int x = 0, int k = 0) {
sum = k, l = r = x;
mxans = lmxsum = rmxsum = k, rmxpos = lmxpos = lmxanspos = rmxanspos = x;
mnans = lmnsum = rmnsum = k, rmnpos = lmnpos = lmnanspos = rmnanspos = x;
}
inline void opposite() {
sum = -sum, mxans = -mxans, mnans = -mnans;
lmxsum = -lmxsum, lmnsum = -lmnsum, rmxsum = -rmxsum, rmnsum = -rmnsum;
swap(mxans, mnans), swap(lmxanspos, lmnanspos), swap(rmxanspos, rmnanspos);
swap(lmxsum, lmnsum), swap(rmxsum, rmnsum), swap(rmxpos, rmnpos), swap(lmxpos, lmnpos);
}
inline friend Node operator + (Node a, Node b) {
Node c;
c.sum = a.sum + b.sum, c.l = a.l, c.r = b.r;
if (a.sum + b.lmxsum > a.lmxsum)
c.lmxsum = a.sum + b.lmxsum, c.rmxpos = b.rmxpos;
else
c.lmxsum = a.lmxsum, c.rmxpos = a.rmxpos;
if (a.sum + b.lmnsum < a.lmnsum)
c.lmnsum = a.sum + b.lmnsum, c.rmnpos = b.rmnpos;
else
c.lmnsum = a.lmnsum, c.rmnpos = a.rmnpos;
if (b.sum + a.rmxsum > b.rmxsum)
c.rmxsum = b.sum + a.rmxsum, c.lmxpos = a.lmxpos;
else
c.rmxsum = b.rmxsum, c.lmxpos = b.lmxpos;
if (b.sum + a.rmnsum < b.rmnsum)
c.rmnsum = b.sum + a.rmnsum, c.lmnpos = a.lmnpos;
else
c.rmnsum = b.rmnsum, c.lmnpos = b.lmnpos;
c.mxans = max(max(c.lmxsum, c.rmxsum), max(a.rmxsum + b.lmxsum, max(a.mxans, b.mxans)));
if (c.mxans == c.lmxsum)
c.lmxanspos = c.l, c.rmxanspos = c.rmxpos;
else if (c.mxans == c.rmxsum)
c.lmxanspos = c.lmxpos, c.rmxanspos = c.r;
else if (c.mxans == a.rmxsum + b.lmxsum)
c.lmxanspos = a.lmxpos, c.rmxanspos = b.rmxpos;
else if (c.mxans == a.mxans)
c.lmxanspos = a.lmxanspos, c.rmxanspos = a.rmxanspos;
else if (c.mxans == b.mxans)
c.lmxanspos = b.lmxanspos, c.rmxanspos = b.rmxanspos;
c.mnans = min(min(c.lmnsum, c.rmnsum), min(a.rmnsum + b.lmnsum, min(a.mnans, b.mnans)));
if (c.mnans == c.lmnsum)
c.lmnanspos = c.l, c.rmnanspos = c.rmnpos;
else if (c.mnans == c.rmnsum)
c.lmnanspos = c.lmnpos, c.rmnanspos = c.r;
else if (c.mnans == a.rmnsum + b.lmnsum)
c.lmnanspos = a.lmnpos, c.rmnanspos = b.rmnpos;
else if (c.mnans == a.mnans)
c.lmnanspos = a.lmnanspos, c.rmnanspos = a.rmnanspos;
else if (c.mnans == b.mnans)
c.lmnanspos = b.lmnanspos, c.rmnanspos = b.rmnanspos;
return c;
}
} s[N << 2];
int tag[N << 2];
inline int ls(int x) {
return x << 1;
}
inline int rs(int x) {
return x << 1 | 1;
}
inline void pushup(int x) {
s[x] = s[ls(x)] + s[rs(x)];
}
inline void spread(int x) {
s[x].opposite(), tag[x] ^= 1;
}
inline void pushdown(int x) {
if (tag[x])
spread(ls(x)), spread(rs(x)), tag[x] = 0;
}
void build(int x, int l, int r) {
s[x].l = l, s[x].r = r;
if (l == r) {
s[x] = Node(l, a[l]);
return;
}
int mid = (l + r) >> 1;
build(ls(x), l, mid), build(rs(x), mid + 1, r);
pushup(x);
}
void update(int x, int nl, int nr, int pos, int k) {
if (nl == nr) {
s[x] = Node(pos, k);
return;
}
pushdown(x);
int mid = (nl + nr) >> 1;
if (pos <= mid)
update(ls(x), nl, mid, pos, k);
else
update(rs(x), mid + 1, nr, pos, k);
pushup(x);
}
void modify(int x, int nl, int nr, int l, int r) {
if (l <= nl && nr <= r) {
spread(x);
return;
}
pushdown(x);
int mid = (nl + nr) >> 1;
if (l <= mid)
modify(ls(x), nl, mid, l, r);
if (r > mid)
modify(rs(x), mid + 1, nr, l, r);
pushup(x);
}
Node query(int x, int nl, int nr, int l, int r) {
if (l <= nl && nr <= r)
return s[x];
pushdown(x);
int mid = (nl + nr) >> 1;
if (r <= mid)
return query(ls(x), nl, mid, l, r);
else if (l > mid)
return query(rs(x), mid + 1, nr, l, r);
else
return query(ls(x), nl, mid, l, r) + query(rs(x), mid + 1, nr, l, r);
}
} // namespace SMT
inline int query(int d, int sum, int l, int r) {
if (!d)
return 0;
auto res = SMT::query(1, 1, n, l, r);
SMT::modify(1, 1, n, res.lmxanspos, res.rmxanspos);
int ans = max(sum + res.mxans, query(d - 1, sum + res.mxans, l, r));
SMT::modify(1, 1, n, res.lmxanspos, res.rmxanspos);
return ans;
}
signed main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
scanf("%d", a + i);
SMT::build(1, 1, n);
scanf("%d", &m);
while (m--) {
int op;
scanf("%d", &op);
if (op) {
int l, r, k;
scanf("%d%d%d", &l, &r, &k);
printf("%d\n", query(k, 0, l, r));
} else {
int x, k;
scanf("%d%d", &x, &k);
SMT::update(1, 1, n, x, k);
}
}
return 0;
}
CF2063D Game With Triangles
平面上有 \(n + m\) 个点 \((a_{1 \sim n}, 0)\) 、\((b_{1 \sim m}, 2)\) 。每次可以选择三个点组成三角形并删去,分数为其面积。
求最多操作次数为 \(k_{\max}\) ,并对于操作 \(1 \sim k_{\max}\) 次求出最大得分。
\(n, m \leq 2 \times 10^5\)
显然有 \(k_{\max} = \min \{ n, m, \frac{n + m}{3} \}\) ,面积即为纵坐标相同点的横坐标差。
考虑反悔贪心,则每次可以选择:
- 选一个 \(a\) 为边的三角形。
- 选一个 \(b\) 为边的三角形。
- 撤销一个 \(a\) 为边的三角形,选两个 \(b\) 为边的三角形。
- 撤销一个 \(b\) 为边的三角形,选两个 \(a\) 为边的三角形。
不难发现为了最大化面积,固定顶点是 \(a\) 还是 \(b\) 后贪心选取另一边最左和最右的两个点最优。于是维护堆存储两端线段长度即可做到 \(O(n \log n)\) 。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const int N = 2e5 + 7;
int a[N], b[N];
int n, m;
signed main() {
int T;
scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i)
scanf("%d", a + i);
for (int i = 1; i <= m; ++i)
scanf("%d", b + i);
sort(a + 1, a + n + 1), sort(b + 1, b + m + 1);
int kmax = min(min(n, m), (n + m) / 3);
printf("%d\n", kmax);
priority_queue<int> qa1, qa2, qb1, qb2;
for (int i = 1; i <= n / 2; ++i)
qa1.emplace(a[n - i + 1] - a[i]);
for (int i = 1; i <= m / 2; ++i)
qb1.emplace(b[m - i + 1] - b[i]);
ll ans = 0;
for (int i = 1, n1 = 0, n2 = 0, m1 = 0, m2 = 0; i <= kmax; ++i) {
ll res1 = -inf, res2 = -inf, res3 = -inf, res4 = -inf;
if (2 * (n2 + 1) + n1 <= n && 2 * m2 + (m1 + 1) <= m)
res1 = qa1.top();
if (2 * (m2 + 1) + m1 <= m && 2 * n2 + (n1 + 1) <= n)
res2 = qb1.top();
if (n2 && m1 && 2 * (n2 - 1) + (n1 + 2) <= n && 2 * (m2 + 2) + (m1 - 1) <= m) {
res3 = qa2.top() + qb1.top();
int tmp = qb1.top();
qb1.pop(), res3 += qb1.top(), qb1.emplace(tmp);
}
if (m2 && n1 && 2 * (m2 - 1) + (m1 + 2) <= m && 2 * (n2 + 2) + (n1 - 1) <= n) {
res4 = qb2.top() + qa1.top();
int tmp = qa1.top();
qa1.pop(), res4 += qa1.top(), qa1.emplace(tmp);
}
ll res = max(max(res1, res2), max(res3, res4));
printf("%lld ", ans += res);
if (res1 == res)
++n2, ++m1, qa2.emplace(-qa1.top()), qa1.pop();
else if (res2 == res)
++m2, ++n1, qb2.emplace(-qb1.top()), qb1.pop();
else if (res3 == res) {
--n2, n1 += 2, m2 += 2, --m1;
qa1.emplace(-qa2.top()), qa2.pop();
qb2.emplace(-qb1.top()), qb1.pop();
qb2.emplace(-qb1.top()), qb1.pop();
} else {
--m2, m1 += 2, n2 += 2, --n1;
qb1.emplace(-qb2.top()), qb2.pop();
qa2.emplace(-qa1.top()), qa1.pop();
qa2.emplace(-qa1.top()), qa1.pop();
}
}
puts("");
}
return 0;
}
QOJ9845. Reverse LIS
给定一个 01 串,其被分为 \(n\) 段,第 \(i\) 段由 \(p_i\) 个 \(0\) 或 \(1\) 组成。
你可以对其进行不超过 \(k\) 次翻转子串操作,最大化操作后的串的最长不下降子序列的长度。
\(n \leq 2 \times 10^5\)
首先可以发现翻转的子串一定是整段,这可以用调整法证明。
由于只有 \(0\) 和 \(1\) ,因此最长不下降子序列一定存在一个分界点 \(p\) ,其形如 \(\leq p\) 的 \(0\) 拼上 \(> p\) 的 \(1\) 。
考虑将 \(0\) 的权值记为 \(1\) ,\(1\) 的权值记为 \(-1\) ,权值的前缀和记为 \(s_i\) ,\(1\) 的数量记为 \(cnt\) ,则固定分界点 \(p\) 后最长不下降子序列的长度即为 \(cnt + s_p\) ,问题转化为区间翻转后最大化最大前缀和。
可以发现每次翻转的区间不交,这可以用调整法证明,问题转化为选出一段前缀和 \(\leq k\) 段两两区间(将他们翻转到前缀),最大化选出的权值和。
考虑反悔贪心,先贪心选取最大前缀,然后每次贪心选取最大子段和,并将选取的区间取反以实现反悔操作,用线段树维护这个过程即可做到 \(O(n \log n)\) 。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 2e5 + 7;
ll s[N], ans[N];
int a[N];
int n, q;
namespace SMT {
struct Node {
ll sum, mxans, mnans, lmxsum, lmnsum, rmxsum, rmnsum;
int l, r, lmxpos, lmnpos, rmxpos, rmnpos, lmxanspos, rmxanspos, lmnanspos, rmnanspos;
inline Node(int x = 0, int k = 0) {
sum = k, l = r = x;
mxans = lmxsum = rmxsum = k, rmxpos = lmxpos = lmxanspos = rmxanspos = x;
mnans = lmnsum = rmnsum = k, rmnpos = lmnpos = lmnanspos = rmnanspos = x;
}
inline void opposite() {
sum = -sum, mxans = -mxans, mnans = -mnans;
lmxsum = -lmxsum, lmnsum = -lmnsum, rmxsum = -rmxsum, rmnsum = -rmnsum;
swap(mxans, mnans), swap(lmxanspos, lmnanspos), swap(rmxanspos, rmnanspos);
swap(lmxsum, lmnsum), swap(rmxsum, rmnsum), swap(rmxpos, rmnpos), swap(lmxpos, lmnpos);
}
inline friend Node operator + (Node a, Node b) {
Node c;
c.sum = a.sum + b.sum, c.l = a.l, c.r = b.r;
if (a.sum + b.lmxsum > a.lmxsum)
c.lmxsum = a.sum + b.lmxsum, c.rmxpos = b.rmxpos;
else
c.lmxsum = a.lmxsum, c.rmxpos = a.rmxpos;
if (a.sum + b.lmnsum < a.lmnsum)
c.lmnsum = a.sum + b.lmnsum, c.rmnpos = b.rmnpos;
else
c.lmnsum = a.lmnsum, c.rmnpos = a.rmnpos;
if (b.sum + a.rmxsum > b.rmxsum)
c.rmxsum = b.sum + a.rmxsum, c.lmxpos = a.lmxpos;
else
c.rmxsum = b.rmxsum, c.lmxpos = b.lmxpos;
if (b.sum + a.rmnsum < b.rmnsum)
c.rmnsum = b.sum + a.rmnsum, c.lmnpos = a.lmnpos;
else
c.rmnsum = b.rmnsum, c.lmnpos = b.lmnpos;
c.mxans = max(max(c.lmxsum, c.rmxsum), max(a.rmxsum + b.lmxsum, max(a.mxans, b.mxans)));
if (c.mxans == c.lmxsum)
c.lmxanspos = c.l, c.rmxanspos = c.rmxpos;
else if (c.mxans == c.rmxsum)
c.lmxanspos = c.lmxpos, c.rmxanspos = c.r;
else if (c.mxans == a.rmxsum + b.lmxsum)
c.lmxanspos = a.lmxpos, c.rmxanspos = b.rmxpos;
else if (c.mxans == a.mxans)
c.lmxanspos = a.lmxanspos, c.rmxanspos = a.rmxanspos;
else if (c.mxans == b.mxans)
c.lmxanspos = b.lmxanspos, c.rmxanspos = b.rmxanspos;
c.mnans = min(min(c.lmnsum, c.rmnsum), min(a.rmnsum + b.lmnsum, min(a.mnans, b.mnans)));
if (c.mnans == c.lmnsum)
c.lmnanspos = c.l, c.rmnanspos = c.rmnpos;
else if (c.mnans == c.rmnsum)
c.lmnanspos = c.lmnpos, c.rmnanspos = c.r;
else if (c.mnans == a.rmnsum + b.lmnsum)
c.lmnanspos = a.lmnpos, c.rmnanspos = b.rmnpos;
else if (c.mnans == a.mnans)
c.lmnanspos = a.lmnanspos, c.rmnanspos = a.rmnanspos;
else if (c.mnans == b.mnans)
c.lmnanspos = b.lmnanspos, c.rmnanspos = b.rmnanspos;
return c;
}
} nd[N << 2];
int tag[N << 2];
inline int ls(int x) {
return x << 1;
}
inline int rs(int x) {
return x << 1 | 1;
}
inline void pushup(int x) {
nd[x] = nd[ls(x)] + nd[rs(x)];
}
inline void spread(int x) {
nd[x].opposite(), tag[x] ^= 1;
}
inline void pushdown(int x) {
if (tag[x])
spread(ls(x)), spread(rs(x)), tag[x] = 0;
}
void build(int x, int l, int r) {
nd[x].l = l, nd[x].r = r;
if (l == r) {
nd[x] = Node(l, a[l]);
return;
}
int mid = (l + r) >> 1;
build(ls(x), l, mid), build(rs(x), mid + 1, r);
pushup(x);
}
void update(int x, int nl, int nr, int l, int r) {
if (l <= nl && nr <= r) {
spread(x);
return;
}
pushdown(x);
int mid = (nl + nr) >> 1;
if (l <= mid)
update(ls(x), nl, mid, l, r);
if (r > mid)
update(rs(x), mid + 1, nr, l, r);
pushup(x);
}
} // namespace SMT
signed main() {
scanf("%d", &n);
ll sum = 0;
for (int i = 1; i <= n; ++i) {
int c, p;
scanf("%d%d", &c, &p);
if (c)
a[i] = -p, ans[0] += p;
else
a[i] = p;
s[i] = s[i - 1] + a[i], sum += p;
}
SMT::build(1, 1, n);
int p = max_element(s, s + n + 1) - s;
ans[0] += s[p];
if (p)
SMT::update(1, 1, n, 1, p);
p = 0;
while (SMT::nd[1].mxans > 0) {
++p, ans[p] = ans[p - 1] + SMT::nd[1].mxans;
SMT::update(1, 1, n, SMT::nd[1].lmxanspos, SMT::nd[1].rmxanspos);
}
scanf("%d", &q);
while (q--) {
int k;
scanf("%d", &k);
printf("%lld\n", ans[min(k, p)]);
}
return 0;
}