反悔贪心
反悔贪心
一般贪心仅能解出局部最优解,显然存在题目不满足局部最优解等价于全局最优解,于是引入反悔贪心。
实现
每次都进行操作,若以后有更优情况,撤销这次操作(反悔),本质为跑一遍局部最优假贪心,再通过反悔操作得到答案。
不难发现反悔贪心的核心在于维护反悔操作所需的值,所以当有些题目可以很方便地维护出操作贡献时,可以考虑反悔贪心。
可以参考一个好用的大根堆:
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\) 项任务的截止时间为 \(d_i\) ,价值为 \(p_i\) ,求最大价值。
\(n \leq 10^5\)
首先按截止时间排序。若当前任务可以做,就先去做;否则若当前任务价值高于做过的最小价值的任务,则将其替换为该任务。
时间复杂度 \(O(n \log n)\) 。
#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;
}
有 \(n\) 个任务,第 \(i\) 个任务需要花费 \(t_i\) 的时间,截止时间为 \(d_i\) ,求最多完成几件任务。
\(n \leq 1.5 \times 10^5\)
与上题类似。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 1.5e5 + 7;
struct Node {
int t, d;
inline bool operator < (const Node &rhs) const {
return d < rhs.d;
}
} a[N];
priority_queue<int> q;
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].t = read(), a[i].d = read();
sort(a + 1, a + 1 + n);
ll now = 0;
for (int i = 1; i <= n; ++i)
if (now + a[i].t <= a[i].d)
q.emplace(a[i].t), now += a[i].t;
else if (a[i].t < q.top())
now -= q.top(), q.pop(), q.emplace(a[i].t), now += a[i].t;
printf("%d", q.size());
return 0;
}
已知接下来 \(n\) 天的股票价格,每一天可以选择买进、卖出或不做,求 \(n\) 天后的最大利润。
\(n \leq 3 \times 10^5\)
对于每一个形如“第 \(i\) 天买入,第 \(j\) 天卖出”的决策,假想出一个价格为 \(val\) 的物品,使得“第 \(i\) 天买入,第 \(j\) 天卖出,同时买入这个价格为 \(val\) 的物品,并在第 \(k\) 天卖出“等价于"第 \(i\) 天买入,第 \(k\) 天卖出",就实现了反悔操作。
设第 \(i\) 天的价格为 \(a_i\) ,则 \((a_j - a_i) + (a_k - val) = a_k - a_i\) ,化简得 \(val = a_j\) 。
于是维护一个堆代表可选物品价格,从前向后遍历每一天。对于第 \(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;
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\) 天上午会进货 \(A_i\) 件商品,中午会有顾客购买 \(B_i\) 件商品,可选择满足或无视。求最多能够满足多少顾客的需求。
\(n \leq 2.5 \times 10^5\)
每次能选就选,不能选时如果之前选的最大数大于当前数,那显然用当前数替换之前最大数要更优。
时间复杂度 \(O(n \log n)\) 。
#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;
}
CF730I Olympiad in Programming and Sports
有 \(n\) 个学生,第 \(i\) 个人有 \(a_i\) 的编程能力和 \(b_i\) 的运动能力。需要将他们分为 \(p\) 人的编程团队和 \(s\) 人的体育团队。一个学生只能参加其中一项,每个团队的力量是其成员在对应领域能力总和。求两个团队的实力和最大的方案。
\(n \leq 3 \times 10^3\)
先选 \(p\) 个 \(a\) 最大的点,后面考虑加 \(s\) 个点进另一个集合, 无疑两种情况。
- 找一个选进集合 \(p\) 的 \(i\) ,和一个不在任何集合的 \(j\) ,将 \(i\) 放入集合 \(s\) ,\(j\) 放入集合 \(p\), 增量是 \((b_i − a_i) + a_j\)
- 找一个没选进任何集合的 \(i\),放入集合 \(s\) ,增量是 \(b_i\) 。 所以就只用三个堆维护 \(a_i , b_i , b_i − a_i\) 即可。
求 \(n\) 个数中选出至多 \(k\) 个两两不相邻的数的最大和。
\(n \leq 3 \times 10^5\)
设当前选出的最大的点为 \(id\) ,它左右两边的点分别是 \(x, y\) ,就删掉这三个点并新建一个点权为 \(a_x + a_y - a_{id}\) 的,这样下次选出这个点 \(p\) 时就实现了反悔操作。因为现在舍弃了 \(id\) ,所以之后也不会选 \(id\) ,故正确。用链表维护相邻点即可做到 \(O(n \log n)\) 。
#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;
}
在长度为 \(n\) 的有序序列中选出互异的 \(2k\) 个数构成 \(k\) 个二元组,求每个二元组元素差值的和的最小值
把每个相邻的数字的差值抽象成为 \(n - 1\) 个点,那么当选择第 \(i\) 个点时,我们就不能选 \(i - 1, i + 1\) 的点,后续思路同上。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const ll inf = 1e18;
const int N = 1e5 + 7;
struct List {
int pre, nxt;
ll val;
} l[N];
priority_queue<pair<ll, int> > q;
int a[N];
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)
a[i] = read<ll>();
for (int i = 1; i < n; ++i) {
l[i] = (List) {i - 1, i + 1, a[i + 1] - a[i]};
q.emplace(-(a[i + 1] - a[i]), i);
}
l[0].val = l[n].val = inf;
ll ans = 0;
while (k--) {
while (vis[q.top().second])
q.pop();
ll val = -q.top().first;
int id = q.top().second;
q.pop();
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;
}
\(n\) 个关卡,对每个关卡,你可以花 \(a_i\) 代价得到一颗星,也可以花 \(b_i\) 代价得到两颗星,也可以不玩,求获得 \(w\) 颗星最少需要多少时间
考虑如何从选了 \(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)\) 。
#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\) 头初始价格为 \(p_i\) ,使用优惠券时价格为 \(c_i\) 。你有 \(k\) 张优惠券和 \(m\) 元,求购买奶牛的最大数量。
\(n \leq 5 \times 10^4\)
能够使买入奶牛数量增加的三种操作:
- 用优惠券买一头未买的奶牛
- 不用优惠券买一头未买的奶牛
- 将一头用优惠券买的奶牛替换为不用优惠券买的同一奶牛,并用优惠券买入一头未买的奶牛
#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;
}
给定两个长度为 \(n\) 的正整数序列 \(\{a_i\}\) 与 \(\{b_i\}\),序列的下标为 \(1, 2, \cdots , 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\) 。
#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;
}
有 \(x + y + z\) 个人,第 \(i\) 人有 \(a_i\) 金币、\(b_i\) 银币、\(c_i\) 铜币。要选出 \(x\) 个人获得其金币,\(y\) 个人获得其银币,\(z\) 个人获得其铜币,在选人不重复的情况下求币总数的最大值。
\(x + y + z \leq 10^5\)
考虑先随便整出一种合法的方案,然后反悔操作都是类似于环的变换,维护 \(5\) 种环状变换对应的堆即可,若附加价值为负则达到最大值。
#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;
}
\(n\) 道题, 第 \(i\) 天可以花费 \(a_i\) 准备一道题, 花费 \(b_i\) 打印一道题, 每天最多准备一道, 最多打印一道, 准备的题可以留到以后打印, 求最少花费使得准备并打印 \(k\) 道题。
\(n \leq 5 \times 10^5\)
恰好 \(k\) 道题,一眼 wqs 二分。于是可以抛开这个条件,再来看这道题。
发现我们每次决策的时候可以在打印的时候考虑,打印时考虑打印哪天准备的题目。贪心地来讲,我们一定会选择当前准备+打印总花费最少的题目。
进一步而言,对于每一次打印,我们一定会挑选之前准备花费最少且准备+打印总花费为非正数的题目进行打印。但是这显然还不够。
很显然的反例是,之前准备花费最小的题目在最优情况下,本来应该被后面的打印挑走,却被前面的打印先挑走了。这显然是不行的。
于是考虑反悔,用一个小根堆维护之前的所有决策,每次取出价格最低的决策,将当前打印的花费加上决策花费后,如果总花费为非正数,则先选择该决策。又由于:
于是把当前打印的花费的相反数加入堆中,供以后反悔使用即可。
还需要特别注意的一点是,由于 wqs 二分时应尽量多选择物品,所以当正常选的决策和反悔的决策花费相同时,我们应该优先选择正常选的决策,使得物品数更多,防止斜率相同的情况发生而无法处理。
#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;
}
给出一个长度为 \(n\) 的数列, \(m\) 次操作,每次有两种:
- 修改某个位置的值。
- 询问区间\([l,r]\) 里选出至多 \(k\) 个不相交的子段和的最大值。
\(n, m \leq 10^5, a_i \leq 500, 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;
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, tag;
int lmxsum, lmxpos, lmnsum, lmnpos;
int rmxsum, rmxpos, rmnsum, rmnpos;
int mxans, lmxanspos, rmxanspos;
int mnans, lmnanspos, rmnanspos;
inline Node(const int pos = 0, const int val = 0) {
sum = val, tag = 0, l = r = pos;
mxans = lmxsum = rmxsum = val, rmxpos = lmxpos = lmxanspos = rmxanspos = pos;
mnans = lmnsum = rmnsum = val, rmnpos = lmnpos = lmnanspos = rmnanspos = pos;
}
inline void opposite() {
sum = -sum, tag ^= 1;
swap(lmxsum, lmnsum), swap(rmxsum, rmnsum);
swap(rmxpos, rmnpos), swap(lmxpos, lmnpos);
swap(mxans, mnans), mxans = -mxans, mnans = -mnans;
lmxsum = -lmxsum, lmnsum = -lmnsum, rmxsum = -rmxsum, rmnsum = -rmnsum;
swap(lmxanspos, lmnanspos), swap(rmxanspos, rmnanspos);
}
inline Node operator + (const Node &rhs) const {
Node res;
res.sum = sum + rhs.sum, res.tag = 0, res.l = l, res.r = rhs.r;
if (sum + rhs.lmxsum > lmxsum)
res.lmxsum = sum + rhs.lmxsum, res.rmxpos = rhs.rmxpos;
else
res.lmxsum = lmxsum, res.rmxpos = rmxpos;
if (sum + rhs.lmnsum < lmnsum)
res.lmnsum = sum + rhs.lmnsum, res.rmnpos = rhs.rmnpos;
else
res.lmnsum = lmnsum, res.rmnpos = rmnpos;
if (rhs.sum + rmxsum > rhs.rmxsum)
res.rmxsum = rhs.sum + rmxsum, res.lmxpos = lmxpos;
else
res.rmxsum = rhs.rmxsum, res.lmxpos = rhs.lmxpos;
if (rhs.sum + rmnsum < rhs.rmnsum)
res.rmnsum = rhs.sum + rmnsum, res.lmnpos = lmnpos;
else
res.rmnsum = rhs.rmnsum, res.lmnpos = rhs.lmnpos;
res.mxans = max(max(res.lmxsum, res.rmxsum), max(rmxsum + rhs.lmxsum, max(mxans, rhs.mxans)));
if (res.mxans == res.lmxsum)
res.lmxanspos = res.l, res.rmxanspos = res.rmxpos;
else if (res.mxans == res.rmxsum)
res.lmxanspos = res.lmxpos, res.rmxanspos = res.r;
else if (res.mxans == rmxsum + rhs.lmxsum)
res.lmxanspos = lmxpos, res.rmxanspos = rhs.rmxpos;
else if (res.mxans == mxans)
res.lmxanspos = lmxanspos, res.rmxanspos = rmxanspos;
else if (res.mxans == rhs.mxans)
res.lmxanspos = rhs.lmxanspos, res.rmxanspos = rhs.rmxanspos;
res.mnans = min(min(res.lmnsum, res.rmnsum), min(rmnsum + rhs.lmnsum, min(mnans, rhs.mnans)));
if (res.mnans == res.lmnsum)
res.lmnanspos = res.l, res.rmnanspos = res.rmnpos;
else if (res.mnans == res.rmnsum)
res.lmnanspos = res.lmnpos, res.rmnanspos = res.r;
else if (res.mnans == rmnsum + rhs.lmnsum)
res.lmnanspos = lmnpos, res.rmnanspos = rhs.rmnpos;
else if (res.mnans == mnans)
res.lmnanspos = lmnanspos, res.rmnanspos = rmnanspos;
else if (res.mnans == rhs.mnans)
res.lmnanspos = rhs.lmnanspos, res.rmnanspos = rhs.rmnanspos;
return res;
}
} s[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 pushdown(int x) {
if (s[x].tag)
s[ls(x)].opposite(), s[rs(x)].opposite(), s[x].tag = 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) {
s[x].opposite();
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 ask(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 ask(ls(x), nl, mid, l, r);
else if (l > mid)
return ask(rs(x), mid + 1, nr, l, r);
else
return ask(ls(x), nl, mid, l, r) + ask(rs(x), mid + 1, nr, l, r);
}
inline int query(int d, int sum, int l, int r) {
if (!d)
return 0;
Node res = ask(1, 1, n, l, r);
modify(1, 1, n, res.lmxanspos, res.rmxanspos);
int ans = max(sum + res.mxans, query(d - 1, sum + res.mxans, l, r));
modify(1, 1, n, res.lmxanspos, res.rmxanspos);
return ans;
}
} // namespace SMT
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", SMT::query(k, 0, l, r));
} else {
int x = read(), k = read();
SMT::update(1, 1, n, x, k);
}
}
return 0;
}