CF1251E2

非常神的贪心,先要发现以下两个性质:

  • 要花钱收买的一些人,那么肯定是在一开始就收买他们。
  • 按照 \(m\) 升序排序,那么处理 \(m=x\) 时,\(m=1\sim x-1\) 的人一定都投了票,不管是贿赂还是跟风。

性质一不难理解,而性质二基于性质一,在一开始收买完人后就像连锁反应一样,\(m\) 从小到大开始依次跟风。

当我们处理到 \(m=x\) 的时候,肯定是希望这些人尽可能地跟风,减少花费。

那这就要求已经投过的人尽量地多。

如果 \(m=x\) 的人无法跟风,那 \(m\gt x\) 的人肯定更没法跟风。

所以只能考虑 \(m\lt x\) 的人,按照性质二,他们一定都投票了。如果此时还没法让 \(m=x\) 的人跟风,那就只能在 \(m\ge x\) 的人里选一些贿赂。

还是要记住,我们贿赂的这些人是在一开始就贿赂了的。

于是开 \(n\)vector 来存储每种 \(m\) 对应的所有 \(p\),倒序遍历,如果后面买的人加上前面所有人的数量仍然小于当前的 \(m\),就从后面没买的人中选若干个贿赂,这可以用一个小根堆来实现。

时间复杂度 \(\mathcal O(n\log n)\)

Code:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 200005;
int T;
int n;
vector <int> vec[N];
priority_queue <int, vector <int>, greater <int> > q;
ll ans;

void solve() {
	scanf("%d", &n);
	for (int i = 0; i < n; ++i) vec[i].clear();
	while (!q.empty()) q.pop();
	for (int i = 1, x, y; i <= n; ++i) scanf("%d%d", &x, &y), vec[x].push_back(y);
	int tot = n, cur = 0; ans = 0;
	for (int i = n - 1; ~i; --i) {
		if (vec[i].empty()) continue;
		tot -= vec[i].size(); for (auto x : vec[i]) q.push(x);
		while (cur < i - tot) ++cur, ans += q.top(), q.pop();
	}
	printf("%lld\n", ans);
}

int main() {
	scanf("%d", &T);
	while (T--) solve();
	return 0;
}
posted @ 2022-11-25 08:14  Kobe303  阅读(15)  评论(0编辑  收藏  举报