P8272

[USACO22OPEN] Apple Catching G

题目描述

天上下苹果了!在某些时刻,一定数量的苹果会落到数轴上。在某些时刻,Farmer John 的一些奶牛将到达数轴并开始接苹果。

如果一个苹果在没有奶牛接住的情况下落到数轴上,它就会永远消失。如果一头奶牛和一个苹果同时到达,奶牛就会接住苹果。每头奶牛每秒可以移动一单位距离。一旦一头奶牛接住了一个苹果,她就会离开数轴。

如果 FJ 的奶牛以最优方式合作,她们总共能接住多少个苹果?

输入格式

输入的第一行包含 \(N\)\(1\le N\le 2\cdot 10^5\)),为苹果落到数轴上的次数或 FJ 的奶牛出现的次数。

以下 \(N\) 行每行包含四个整数 \(q_i\)\(t_i\)\(x_i\)\(n_i\)\(q_i\in \{1,2\}, 0\le t_i\le 10^9, 0\le x_i\le 10^9, 1\le n_i\le 10^3\))。

  • 如果 \(q_i=1\),意味着 FJ 的 \(n_i\) 头奶牛在 \(t_i\) 时刻来到数轴上的 \(x_i\) 位置。
  • 如果 \(q_i=2\),意味着 \(n_i\) 个苹果在 \(t_i\) 时刻落到了数轴上的 \(x_i\) 位置。

输入保证所有有序对 \((t_i,x_i)\) 各不相同。


模拟赛被创死,展现出普及组选手 cfm 强大的普及水准。

什么时候 \(i\) 可以接住苹果 \(a\)?(\(t_i \ge t_a\)

  • \(x_i \ge x_a\) 时,有 \(t_i - t_a \ge x_i - x_a\)\(t_i - x_i \ge t_a - x_a\)
  • \(x_i < x_a\) 时,有 \(t_i - t_a \ge x_a - x_i\)\(t_i + x_i \ge t_a + x_a\)

赛时的时候列出了后面两个式子,但是仍然并不会这道题。

我们注意到后面两个式子一定同时成立!这一点在移向前的式子非常明显,因为 \(t_i - t_ a\ge |x_i - x_a|\)。但是移向后似乎就没有这么显然了(upd:其实也非常显然,上下两式相加得到 \(t_i \ge t_a\)……,反过来只要减一下就好了……)

因此令 \(t_i - x_i = x_i, t_i + x_i = y_i\),那么 \(i\) 可以接住苹果 \(a\) 的充要条件就是 \(x_i \ge x_a, y_i \ge y_a\)。(这里的 \(x\) 和上面的 \(x\) 不是一样的!)这是一个二维偏序问题,但一般的二维偏序是计数,这里是最优化:将数对匹配。

回顾二维偏序的过程,是先对 \(x\) 进行排序,然后利用数据结构维护 \(y\)。这里我们也按照 \(x\) 排序以此消除 \(x_i \ge x_a\) 的约束。对于每个奶牛 \(i\) 考虑把能吃掉的苹果放到集合 \(S\) 中。从 \(x\) 小的奶牛往 \(x\) 大的奶牛考虑。不考虑删除时这个 \(S\) 是在增长的。接下来考虑删除,显然 \(y\) 越小的苹果越难被匹配。所以从 \(y\) 尽量小的奶牛开始匹配。

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5;
struct node {
	int opt, x, y, num;
	bool operator < (const node &other) const {
		return y < other.y;
	}
} Q[N + 10];
bool cmp(node &x, node &y) {
	return x.x > y.x;
}
multiset <node> S;

int n;
vector <node> vec[3];
int main() {
	cin >> n;
	for(int i = 1, t, x; i <= n; i++) {
		cin >> Q[i].opt >> t >> x >> Q[i].num;
		Q[i].x = t - x, Q[i].y = t + x;
		vec[Q[i].opt].push_back(Q[i]);
	}
	
	sort(vec[1].begin(), vec[1].end(), cmp);
	sort(vec[2].begin(), vec[2].end(), cmp);
	
	int ia = 0, sum = 0;
	for(int i = 0; i < vec[1].size(); i++) {
		while(ia < vec[2].size() &&
			vec[2][ia].x >= vec[1][i].x) {
				S.insert(vec[2][ia]);
				ia++;
			}
			
		while(1) {
			if(!vec[1][i].num) break;
			set <node>::iterator it = S.lower_bound(vec[1][i]);
			if(it == S.end()) break;
			
			node tmp = (*it);
			if(tmp.num >= vec[1][i].num) {
				sum += vec[1][i].num;
				tmp.num -= vec[1][i].num;
				vec[1][i].num = 0;
				S.erase(it);
				S.insert(tmp);
				break;
			}
			else {
				sum += tmp.num;
				vec[1][i].num -= tmp.num;
				S.erase(it);
			}
		}
	}
	cout << sum << endl;
}

总结

真的是一个很值得反思反思的题目。

复盘过程

  • 在得到充要条件的一步,因为一般来讲是要根据 \(x\) 的大小讨论一下的。因此少前置约束是一个值得的动机。
  • 从另一个角度,以少前置约束出发就是 \(t_i - t_a \ge |x_i - x_a|\),等价于 \(t_i - t_a \ge x_i - x_a, t_i - t_a \ge x_a - x_i\) 同时成立。即 \(x \ge |y|\) 等价于 \(x \ge y, x \ge -y\)。这个变形我之前似乎没有用过,记下来
  • 于是得到了一个类二维偏序的匹配问题。对于一个高维问题,最常用的思想是降维。三位偏序二维偏序如此,高维前缀和如此,高维统计也如此。
    • 高维问题
      • 降维
      • 独立维度
  • 接下来的贪心中,是从“难以匹配”的奶牛和苹果出发。这也启发对于匹配类的贪心问题应该从“难以匹配”的元素出发思考策略。
    • 这是因为一般来说匹配问题不带权(比如这道题万一苹果带权似乎就不好贪心了)。难以匹配的赶紧匹配,容易匹配的放到后面匹配也没关系。这是“决策包容性”。
    • 关于这块似乎还要结合几个线段贪心详细总结总结
posted @ 2024-11-16 21:48  SIXIANG32  阅读(3)  评论(0编辑  收藏  举报