反悔贪心

反悔贪心

一般贪心仅能解出局部最优解,显然存在题目不满足局部最优解等价于全局最优解,于是引入反悔贪心。

实现

每次都进行操作,若以后有更优情况,撤销这次操作(反悔),本质为跑一遍局部最优假贪心,再通过反悔操作得到答案。

不难发现反悔贪心的核心在于维护反悔操作所需的值,所以当有些题目可以很方便地维护出操作贡献时,可以考虑反悔贪心。

可以参考一个好用的大根堆:

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;
}

P4053 [JSOI2007] 建筑抢修

\(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;
}

CF865D Buy Low Sell High

已知接下来 \(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\) 即可。

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)\)

#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;
}

P3620 [APIO/CTSC2007] 数据备份

在长度为 \(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;
}

CF436E Cardboard Box

\(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;
}

P5470 [NOI2019] 序列

给定两个长度为 \(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;
}

[AGC018C] Coins

\(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;
}

April Fools' Problem

\(n\) 道题, 第 \(i\) 天可以花费 \(a_i\) 准备一道题, 花费 \(b_i\) 打印一道题, 每天最多准备一道, 最多打印一道, 准备的题可以留到以后打印, 求最少花费使得准备并打印 \(k\) 道题。

\(n \leq 5 \times 10^5\)

恰好 \(k\) 道题,一眼 wqs 二分。于是可以抛开这个条件,再来看这道题。

发现我们每次决策的时候可以在打印的时候考虑,打印时考虑打印哪天准备的题目。贪心地来讲,我们一定会选择当前准备+打印总花费最少的题目。

进一步而言,对于每一次打印,我们一定会挑选之前准备花费最少且准备+打印总花费为非正数的题目进行打印。但是这显然还不够。

很显然的反例是,之前准备花费最小的题目在最优情况下,本来应该被后面的打印挑走,却被前面的打印先挑走了。这显然是不行的。

于是考虑反悔,用一个小根堆维护之前的所有决策,每次取出价格最低的决策,将当前打印的花费加上决策花费后,如果总花费为非正数,则先选择该决策。又由于:

\[a_i+b_j-b_j+b_k=a_i+b_j+(-b_j+b_k) \]

于是把当前打印的花费的相反数加入堆中,供以后反悔使用即可。

还需要特别注意的一点是,由于 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;
}

k-Maximum Subsequence Sum

给出一个长度为 \(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;
}
posted @ 2024-07-18 14:30  我是浣辰啦  阅读(4)  评论(0编辑  收藏  举报