CF2055E Haystacks

\(n\) 个干草堆,标记从 \(1\)\(n\),其中干草堆 \(i\) 包含 \(a_i\) 捆干草。一个干草堆下面隐藏着一根针,但你不知道是哪一个。你的任务是移动干草捆,以确保每个干草堆至少被清空一次,从而检查针是否隐藏在那个特定的干草堆下。然而,这个过程并不简单。一旦干草堆 \(i\) 第一次被清空,它将被分配一个高度限制,不能再包含超过 \(b_i\) 捆干草。

更正式地说,移动的描述如下:

选择两个干草堆 \(i\)\(j\)。如果干草堆 \(i\) 之前没有被清空,或者干草堆 \(i\) 包含的干草捆严格少于 \(b_i\),你可以将正好 \(1\) 捆干草从干草堆 \(j\) 移动到干草堆 \(i\)

注意:在干草堆被清空之前,它没有高度限制,你可以将任意数量的干草捆移动到该干草堆上。

计算确保每个干草堆至少被清空一次所需的最小移动次数,或者报告这不可能。


草堆题。

\(c_i=a_i-\displaystyle\sum_{j=1}^{i-1} b_j-a_j\)

首先我们可以考虑先随便钦定一个操作排列,然后从前往后,清空每一个稻草堆 \(i\)

假如 \(c_i \le 0\),那么 \(a_i\) 就可以放进前面的空位里,消耗 \(a_i\) 次操作。

假如 \(c_i>0\),那么前面的空位不足以容纳 \(a_i\),我们此时需要找到后面的一堆存放 \(c_i\)

我们贪心地,会发现把放不下的部分放到最后一堆一定是最优的。

特殊地,考虑最后一堆,最后一堆里面最后会有 \(\sum a_i + \displaystyle\max_{i=1}^n c_i\) 个稻草,只要前面的位置放得下,我们就可以将他们全部移走来清空最后一堆。

不难发现,答案就是 \(\sum a_i + \displaystyle\max_{i=1}^n c_i\)\(\sum a_i\) 是常数,所以我们只要考虑最小化 \(\displaystyle\max_{i=1}^n c_i\) 即可。

首先把满足 \(a_i \le b_i\) 的放在 \(a_i > b_i\) 的前面肯定是不劣的。

然后我们考虑两个位置 \(x<y\),我们会交换他们两个当且仅当 \(\max(a_x,a_x-b_x+a_y)>\max(a_y,a_y-b_y+a_x)\),拆开得到:

\[(a_x>a_y \land a_x>a_y-b_y+a_x) \lor (a_x-b_x+a_y>a_y \land a_x-b_x+a_y>a_y-b_y+a_x) \]

化简得到:

\[(a_x>a_y \land a_y-b_y<0) \lor (a_x-b_x>0 \land b_x<b_y) \]

由此可以推出,当 \(a_x-b_x<0\) 时,我们应按 \(a_x\) 降序排序,当 \(a_x-b_x>0\) 时,我们应按 \(b_x\) 升序排序,此时是最优的。

但是直接排序然后暴力并不是对的,因为最后一个数是需要特殊处理的。

因此我们需要枚举最后一位,然后计算最小的答案。

有一种可行的实现方法是,先把序列排序,然后从前往后扫,同时模拟清空的过程,同时在第 \(i\) 位把 \(i-1\) 的前缀的结果放进数据结构里,然后在数据结构里进行模拟,这样最后数据结构里会维护 \(n\) 个结果,每个都被挖了一个空,我们枚举空计算结果即可,以下是一份可能的实现:

#include <algorithm>
#include <iostream>
#include <vector>
#include <set>
using  std::cin, std::cout;
const int N = 5e5 + 7;
#define int long long
int n, h[N], c[N], d = 0, f = 0, e, g[N];
std::set<std::pair<int, int>> s;
struct o {int a, b;} v[N];
int find(int x) {
	static int s[N]; int t = 0;
	while(h[x] != x) s[++t] = x, x = h[x];
	for(int i = t - 1; i >= 1; --i)
		c[s[i]] += c[s[i+1]], h[s[i]] = x;
	return x;
}
bool cmp(o p1,o p2){
	static auto f = [](o x) {return x.a < x.b ? -1 : x.a > x.b ? 1 : 0;};
	return f(p1)!=f(p2) ? f(p1)<f(p2) : f(p1)<=0 ? p1.a<p2.a : p1.b>p2.b;
}
inline void solve() {
	f = d = 0, e = 1e18; cin >> n; 
	for(int i = 1; i <= n; ++i) cin >> v[i].a >> v[i].b, f += v[i].a;
	for(int i = 1; i <= n; ++i) h[i] = i, ::c[i] = 0; 
	std::sort(v + 1, v + n + 1, cmp);
	for(int i = 1, sig = 0, c = 0; i <= n; ++i) {
		{	auto [a, b] = v[i - 1];
			if(a <= sig) sig -= a;
			else c += a - sig, sig = 0;
			sig += b;
		}
		auto [a, b] = v[i]; d += a;
		if(auto t = s.upper_bound({d, n}); t != s.begin()) {
			int j = (--t)->second; ::c[j] += d - t->first;
			for(auto p = s.begin(); p != t; ++p)
				h[p->second] = j, ::c[p->second] += (d - p->first) - ::c[j];
			s.erase(s.begin(), ++t), s.insert({d, j});
		}
		d -= b;
		::c[i] = c, s.insert({sig + d, i});
	}
	for(auto& [x, y]: s)
		g[y] = x - d;
	s.clear();
	for(int i = 1; i <= n; ++i) { 	
		int j = find(i), c = ::c[i] + (j == i ? 0 : ::c[j]);
		if(v[i].a + c <= g[j]) e = std::min(e, f + c);
	}
	cout << (e == 1e18 ? -1 : e) << "\n";
}
signed main() {
	std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	int t; cin >> t; for(; t--; ) solve();
}
posted @ 2025-02-19 19:57  CuteNess  阅读(27)  评论(0)    收藏  举报