反悔贪心
反悔贪心
一般贪心仅能解出局部最优解,显然存在题目不满足局部最优解等价于全局最优解,于是引入反悔贪心。
实现
每次都进行操作,若以后有更优情况,撤销这次操作(反悔),本质为跑一遍局部最优假贪心,再通过反悔操作得到答案。
不难发现反悔贪心的核心在于维护反悔操作所需的值,所以当有些题目可以很方便地维护出操作贡献时,可以考虑反悔贪心。
可以参考一个好用的大根堆:
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(); } inline void emplace(int x, int id) { q.emplace(x, id); } };
应用
P2949 [USACO09OPEN] Work Scheduling G
有 n 项任务,每一时刻可以完成一项任务,第 i 项任务的截止时间为 di ,价值为 pi ,求最大价值。
n≤105
首先按截止时间排序。若当前任务可以做,就先去做;否则若当前任务价值高于做过的最小价值的任务,则将其替换为该任务。
时间复杂度 O(nlogn) 。
类似的题目:P4053 [JSOI2007] 建筑抢修
#include <bits/stdc++.h> typedef long long ll; using namespace std; const int N = 1e5 + 7; priority_queue<int, vector<int>, greater<int> > q; pair<int, int> a[N]; ll ans; int n; template <class T = int> inline T read() { char c = getchar(); bool sign = (c == '-'); while (c < '0' || c > '9') c = getchar(), sign |= (c == '-'); T x = 0; while ('0' <= c && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar(); return sign ? (~x + 1) : x; } signed main() { n = read(); for (int i = 1; i <= n; ++i) a[i].first = read(), a[i].second = read(); sort(a + 1, a + 1 + n); 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); while (!q.empty()) ans += q.top(), q.pop(); printf("%lld", ans); return 0; }
CF865D Buy Low Sell High
已知接下来 n 天的股票价格,每一天可以选择买进、卖出或不做,求 n 天后的最大利润。
n≤3×105
对于每一个形如“第 i 天买入,第 j 天卖出”的决策,假想出一个价格为 val 的物品,使得“第 i 天买入,第 j 天卖出,同时买入这个价格为 val 的物品,并在第 k 天卖出“等价于"第 i 天买入,第 k 天卖出",就实现了反悔操作。
设第 i 天的价格为 ai ,则 (aj−ai)+(ak−val)=ak−ai ,化简得 val=aj 。
于是维护一个堆代表可选物品价格,从前向后遍历每一天。对于第 i 天,找到堆中最小价格 aj 并加入 ai 代表 ai 这一天可选。若 ai>aj ,则把答案加上 ai−aj ,并向集合中再次加入 ai 代表假想得反悔物品,并删除 aj 。
时间复杂度 O(nlogn) 。
#include <bits/stdc++.h> typedef long long ll; using namespace std; priority_queue<ll, vector<ll>, greater<ll> > q; ll ans; int n; template <class T = int> inline T read() { char c = getchar(); bool sign = (c == '-'); while (c < '0' || c > '9') c = getchar(), sign |= (c == '-'); T x = 0; while ('0' <= c && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar(); return sign ? (~x + 1) : x; } signed main() { n = read(); for (int i = 1; i <= n; ++i) { int x = read(); 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 天上午会进货 Ai 件商品,中午会有顾客购买 Bi 件商品,可选择满足或无视。求最多能够满足多少顾客的需求。
n≤2.5×105
每次能选就选,不能选时如果之前选的最大数大于当前数,那显然用当前数替换之前最大数要更优。
时间复杂度 O(nlogn) 。
#include <bits/stdc++.h> typedef long long ll; using namespace std; const int N = 2.5e5 + 7; priority_queue<pair<ll, int> > q; int a[N], b[N]; bool choose[N]; int n; template <class T = int> inline T read() { char c = getchar(); bool sign = c == '-'; while (c < '0' || c > '9') c = getchar(), sign |= c == '-'; T x = 0; while ('0' <= c && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar(); return sign ? (~x + 1) : x; } signed main() { n = read(); for (int i = 1; i <= n; ++i) a[i] = read(); for (int i = 1; i <= n; ++i) b[i] = read(); ll now = 0; for (int i = 1; i <= n; ++i) { now += a[i]; if (now >= b[i]) now -= b[i], q.emplace(b[i], i); else if (!q.empty() && b[i] < q.top().first) now += q.top().first - b[i], q.pop(), q.emplace(b[i], i); } printf("%d\n", 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≤3×105
设当前选出的最大的点为 id ,它左右两边的点分别是 x,y ,就删掉这三个点并新建一个点权为 ax+ay−aid 的,这样下次选出这个点 p 时就实现了反悔操作。因为现在舍弃了 id ,所以之后也不会选 id ,故正确。用链表维护相邻点即可做到 O(nlogn) 。
类似的题目:
- P1792 [国家集训队] 种树 :推广到环上,注意这题是选恰好 m 个。
- P3620 [APIO/CTSC2007] 数据备份 :贪心可以感受到二元组一定相邻,将相邻两个数建点则转化为这题。
#include <bits/stdc++.h> typedef long long ll; using namespace std; const int N = 5e5 + 7; struct List { int pre, nxt; ll val; } l[N]; priority_queue<pair<ll, int> > q; bool vis[N]; int n, k; template <class T = int> inline T read() { char c = getchar(); bool sign = c == '-'; while (c < '0' || c > '9') c = getchar(), sign |= c == '-'; T id = 0; while ('0' <= c && c <= '9') id = (id << 1) + (id << 3) + (c & 15), c = getchar(); return sign ? (~id + 1) : id; } signed main() { n = read(), k = read(); for (int i = 1; i <= n; ++i) { l[i] = (List) {i - 1, i + 1, read<ll>()}; q.emplace(l[i].val, i); } ll ans = 0; while (k--) { while (vis[q.top().second]) q.pop(); int val = q.top().first, id = q.top().second; q.pop(); if (val <= 0) break; ans += val; vis[l[id].pre] = vis[l[id].nxt] = true; l[id].val = l[l[id].pre].val + l[l[id].nxt].val - l[id].val; l[id].pre = l[l[id].pre].pre, l[id].nxt = l[l[id].nxt].nxt; l[l[id].pre].nxt = l[l[id].nxt].pre = id; q.emplace(l[id].val, id); } printf("%lld", ans); return 0; }
CF436E Cardboard Box
有 n 个关卡,每个关卡可以花 ai 代价得到一颗星,也可以花 bi 代价得到两颗星,也可以不玩。求获得 w 颗星最少代价。
n≤3×105
考虑如何从选了 i 颗星的方案扩展得到选 i+1 颗星的方案
- 选一个没有选星星的位置 i ,付出 ai 的代价选一颗星。
- 选一个已选一颗星的位置 i ,付出 bi−ai 的代价选两颗星。
- 选一个已选一颗星的位置 i ,再选一个没有选星星的位置 j ,将原来 i 位置上的星星反悔不选,再在 j 位置上选两颗星,代价为 bj−ai。
- 选一个已选两颗星的位置 i ,再选一个没有选星星的位置 j ,将原来 i 位置上的星星反悔选一颗星星,再在 j 位置上选两颗星,代价为 bj−(bi−ai) 。
用堆维护即可做到 O(nlogn) 。
#include <bits/stdc++.h> typedef long long ll; using namespace std; const ll inf = 1e18; const int N = 3e5 + 7; struct cmp { inline bool operator () (const pair<ll, int> &x, const pair<ll, int> &y) { return x.first > y.first; } }; int ans[N]; class PriorityQueue { protected: priority_queue<pair<ll, int>, vector<pair<ll, int> >, cmp> q; int op; inline void update() { while (!q.empty() && ans[q.top().second] != op) q.pop(); } public: inline PriorityQueue(int _op) : op(_op) {} inline bool empty() { return update(), q.empty(); } inline pair<ll, int> top() { return update(), q.top(); } inline void push(pair<ll, int> x) { q.push(x); } } q1(0), q2(1), q3(1), q4(0), q5(2); ll a[N], b[N]; int n, w; template <class T = int> inline T read() { char c = getchar(); bool sign = c == '-'; while (c < '0' || c > '9') c = getchar(), sign |= c == '-'; T x = 0; while ('0' <= c && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar(); return sign ? (~x + 1) : x; } inline void push0(int x) { ans[x] = 0; q1.push(make_pair(a[x], x)); q4.push(make_pair(b[x], x)); } inline void push1(int x) { ans[x] = 1; q2.push(make_pair(b[x] - a[x], x)); q3.push(make_pair(-a[x], x)); } inline void push2(int x) { ans[x] = 2; q5.push(make_pair(-(b[x] - a[x]), x)); } signed main() { n = read(), w = read(); for (int i = 1; i <= n; ++i) { a[i] = read(), b[i] = read(); q1.push(make_pair(a[i], i)), q4.push(make_pair(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) push1(x); else if (op == 2) push2(x); else if (op == 3) push0(x), push2(y); else push1(x), push2(y); } printf("%lld\n", Ans); for (int i = 1; i <= n; ++i) printf("%d", ans[i]); return 0; }
P3045 [USACO12FEB] Cow Coupons G
有 n 头奶牛,第 i 头初始价格为 pi ,使用优惠券时价格为 ci 。你有 k 张优惠券和 m 元,求购买奶牛的最大数量。
n≤5×104
能够使买入奶牛数量增加的三种操作:
- 用优惠券买一头未买的奶牛。
- 不用优惠券买一头未买的奶牛。
- 将一头用优惠券买的奶牛替换为不用优惠券买的同一奶牛,并用优惠券买入一头未买的奶牛。
用堆维护即可做到 O(nlogn) 。
#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(); } inline void emplace(int x, int id) { q.emplace(x, id); } } q1(0), q2(0), q3(1); int p[N], c[N]; ll m; int n, k; template <class T = int> inline T read() { char c = getchar(); bool sign = (c == '-'); while (c < '0' || c > '9') c = getchar(), sign |= (c == '-'); T x = 0; while ('0' <= c && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar(); return sign ? (~x + 1) : x; } inline void update0(int x) { id[x] = 0; q1.emplace(-p[x], x), q2.emplace(-c[x], x); } inline void update1(int x) { id[x] = 1; q3.emplace(c[x] - p[x], x); } inline void update2(int x) { id[x] = 2; } signed main() { n = read(), k = read(), m = read<ll>(); for (int i = 1; i <= n; ++i) p[i] = read(), c[i] = read(), 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] 序列
给定 a1∼n,b1∼n ,需要分别对两个序列各指定恰好 k 个下标,要求至少有 l 个下标在两个序列中都被指定,最大化这 2k 个下标在序列中对应的元素的总和。
多测,∑n≤106
首先考虑没有 L 限制的贪心,从 a,b 中各选 K 个最大的即可。
对于 L 的限制,考虑反悔贪心,每次选取增量尽可能大的操作反悔,使得每次返回后都能使当前同时出现在两个序列中的点数多一。
- 找一个选了的 ai 和一个选了的 bj ,用 bi 代替 bj ,增量为 bi−bj 。
- 找一个选了的 ai 和一个选了的 bj ,用 aj 代替 ai ,增量为 aj−ai 。
- 找一个选了的 ai,bj ,再找一个没选的位置 k ,选 ak,bk 代替 ai,bj ,增量为 ak+bk−ai−bj 。
- 找一个选了的 ai,bj ,再找一个都选的位置 k ,选 aj,bi 代替 ak,bk ,增量是 aj+bi−ak−bk 。
时间按复杂度 O(nlogn) 。
#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; template <class T = int> inline T read() { char c = getchar(); bool sign = (c == '-'); while (c < '0' || c > '9') c = getchar(), sign |= (c == '-'); T x = 0; while ('0' <= c && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar(); return sign ? (~x + 1) : x; } struct Heap { priority_queue<pair<ll, int> > q; int op, sign; inline Heap(const int _op, const 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 push(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.push(a[x] + b[x], x); } inline void update1(int x) { tag[x] = 1; q1.push(b[x], x), q4.push(a[x], x); } inline void update2(int x) { tag[x] = 2; q2.push(b[x], x), q3.push(a[x], x); } inline void update3(int x) { tag[x] = 3; q6.push(a[x] + b[x], x); } signed main() { int T = read(); while (T--) { n = read(), k = read(), l = read(); while (!qa.empty()) qa.pop(); while (!qb.empty()) qb.pop(); for (int i = 1; i <= n; ++i) qa.emplace(a[i] = read(), i); for (int i = 1; i <= n; ++i) qb.emplace(b[i] = read(), 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 addtion = -inf; int op = -1; if (q1.topval() - q2.topval() > addtion) addtion = q1.topval() - q2.topval(), op = 1; if (q3.topval() - q4.topval() > addtion) addtion = q3.topval() - q4.topval(), op = 2; if (q5.topval() - q2.topval() - q4.topval() > addtion) addtion = q5.topval() - q2.topval() - q4.topval(), op = 3; if (q1.topval() + q3.topval() - q6.topval() > addtion) addtion = q1.topval() + q3.topval() - q6.topval(), op = 4; if (op == 1) { int i = q1.topid(), j = q2.topid(); q1.pop(), q2.pop(), ans += addtion; update3(i), update0(j); } else if (op == 2) { int i = q3.topid(), j = q4.topid(); q3.pop(), q4.pop(), ans += addtion; update3(i), update0(j); } else if (op == 3) { int k = q5.topid(), i = q2.topid(), j = q4.topid(); q5.pop(), q2.pop(), q4.pop(), ans += addtion; 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(), ans += addtion; update3(i), update3(j), update0(k); } } printf("%lld\n", ans); } return 0; }
[AGC018C] Coins
有 x+y+z 个人,第 i 人有 ai 金币、bi 银币、ci 铜币。要选出 x 个人获得其金币,y 个人获得其银币,z 个人获得其铜币,在选人不重复的情况下求币总数的最大值。
x+y+z≤105
考虑先随便整出一种合法的方案,然后反悔操作都是类似于环的重分配操作,一共五种:
- A→B,B→C,C→A 。
- A→C,C→B,B→A 。
- A→B,B→A 。
- A→C,C→A 。
- B→C,C→B 。
开六个堆维护每一步转换的决策即可,时间复杂度 O(nlogn) 。
类似的题目: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 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(); } inline void emplace(int x, int id) { q.emplace(x, id); } } qxy(1), qxz(1), qyz(2), qyx(2), qzx(3), qzy(3); int a[N], b[N], c[N]; int x, y, z; template <class T = int> inline T read() { char c = getchar(); bool sign = (c == '-'); while (c < '0' || c > '9') c = getchar(), sign |= (c == '-'); T x = 0; while ('0' <= c && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar(); return sign ? (~x + 1) : x; } inline void updatex(int x) { id[x] = 1, qxy.emplace(b[x] - a[x], x), qxz.emplace(c[x] - a[x], x); } inline void updatey(int x) { id[x] = 2, qyx.emplace(a[x] - b[x], x), qyz.emplace(c[x] - b[x], x); } inline void updatez(int x) { id[x] = 3, qzx.emplace(a[x] - c[x], x), qzy.emplace(b[x] - c[x], x); } signed main() { x = read(), y = read(), z = read(); for (int i = 1; i <= x + y + z; ++i) a[i] = read(), b[i] = read(), c[i] = read(); 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 (;;) { ll res = -inf, res1 = -inf, res2 = -inf, res3 = -inf, res4 = -inf, res5 = -inf; if (!qxy.empty() && !qyx.empty()) res = max(res, res1 = qxy.top().first + qyx.top().first); if (!qyz.empty() && !qzy.empty()) res = max(res, res2 = qyz.top().first + qzy.top().first); if (!qxz.empty() && !qzx.empty()) res = max(res, res3 = qxz.top().first + qzx.top().first); if (!qxy.empty() && !qyz.empty() && !qzx.empty()) res = max(res, res4 = qxy.top().first + qyz.top().first + qzx.top().first); if (!qxz.empty() && !qzy.empty() && !qyx.empty()) res = max(res, res5 = qxz.top().first + qzy.top().first + qyx.top().first); if (res <= 0) break; ans += res; if (res1 == res) { int x = qxy.top().second, y = qyx.top().second; updatey(x), updatex(y); } else if (res2 == res) { int y = qyz.top().second, z = qzy.top().second; updatez(y), updatey(z); } else if (res3 == res) { int x = qxz.top().second, z = qzx.top().second; updatez(x), updatex(z); } else if (res4 == res) { 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 天可以花费 ai 准备一道题、花费 bi 打印一道题。
每天最多准备一道、最多打印一道,准备的题可以留到以后打印。
求准备并打印 k 道题的最小花费。
n≤5×105
先用 wqs 二分去掉恰好 k 题的限制。考虑在每次打印的时候选取准备花费最少且准备+打印总花费为非正数的题目进行打印,但是这样会使得后面一个打印花费更优的位置无法匹配,于是引入反悔操作。
用一个小根堆维护之前的所有决策,每次取出价格最低的决策,将当前打印的花费加上决策花费后,如果总花费为非正数,则先选择该决策。又由于:
于是把当前打印的花费的相反数加入堆中即可,时间复杂度 O(nlognlogV) 。
#include <bits/stdc++.h> typedef long long ll; using namespace std; const int N = 5e5 + 7; ll a[N], b[N]; int n, k; template <class T = int> inline T read() { char c = getchar(); bool sign = (c == '-'); while (c < '0' || c > '9') c = getchar(), sign |= (c == '-'); T x = 0; while ('0' <= c && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar(); return sign ? (~x + 1) : x; } 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(make_pair(-a[i], 1)); if (-b[i] > -q.top().first) { ans += b[i] - q.top().first, cnt += q.top().second; q.pop(), q.emplace(make_pair(b[i], 0)); } } for (int i = 1; i <= n; ++i) b[i] += lambda; return make_pair(ans + lambda * cnt, cnt); } signed main() { n = read(), k = read(); for (int i = 1; i <= n; ++i) a[i] = read(); for (int i = 1; i <= n; ++i) b[i] = read(); 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≤105 ,k≤20
考虑用线段树维护反悔贪心。处理一个区间询问时贪心选最大子段和,再将其全部取相反数,这样下一次算到时就算反悔。
时间复杂度 O(nklogn) 。
#include <bits/stdc++.h> using namespace std; const int inf = 0x3f3f3f3f; const int N = 1e5 + 7; int a[N]; int n, m; template <class T = int> inline T read() { char c = getchar(); bool sign = (c == '-'); while (c < '0' || c > '9') c = getchar(), sign |= (c == '-'); T x = 0; while ('0' <= c && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar(); return sign ? (~x + 1) : x; } 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() { n = read(); for (int i = 1; i <= n; ++i) a[i] = read(); SMT::build(1, 1, n); m = read(); while (m--) { int op = read(); if (op) { int l = read(), r = read(), k = read(); printf("%d\n", query(k, 0, l, r)); } else { int x = read(), k = read(); SMT::update(1, 1, n, x, k); } } return 0; }
CF2063D Game With Triangles
平面上有 n+m 个点 (a1∼n,0) 、(b1∼m,2) 。每次可以选择三个点组成三角形并删去,分数为其面积。
求最多操作次数为 kmax ,并对于操作 1∼kmax 次求出最大得分。
n,m≤2×105
显然有 kmax=min{n,m,n+m3} ,面积即为纵坐标相同点的横坐标差。
考虑反悔贪心,则每次可以选择:
- 选一个 a 为边的三角形。
- 选一个 b 为边的三角形。
- 撤销一个 a 为边的三角形,选两个 b 为边的三角形。
- 撤销一个 b 为边的三角形,选两个 a 为边的三角形。
不难发现为了最大化面积,固定顶点是 a 还是 b 后贪心选取另一边最左和最右的两个点最优。于是维护堆存储两端线段长度即可做到 O(nlogn) 。
#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; }
本文作者:wshcl
本文链接:https://www.cnblogs.com/wshcl/p/18712932/ReversibleGreedy
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步