CF1607H 题解

· · 题解

CF1607H 题解

Nov/18/2021. Upd:修改了一个笔误

Nov/25/2021. Upd:修改了第二个笔误

题意:给 n 个二元组和一个数组 m[1...n],第 i 个二元组形如 (a_i, b_i)

需要你构造出 n 个新的二元组,要求是:

i 个新二元组 (a'_i, b'_i) 需要满足以下条件:a'_i \leq a_i, b'_i \leq b_i, a'_i + b'_i = a_i + b_i - m_i

请最小化新的 n 个二元组中的本质不同二元组个数并给出方案,

其中两个二元组 (a, b) , (c, d) 本质不同,当且仅当 a \neq cb \neq d

做法

首先,我们定义 val_i = a_i + b_i - m_i,则有 val_i = a'_i + b'_i

故若 val_i \neq val_j,则 (a'_i,b'_i)(a'_j, b'_j) 必然本质不同。

那么,我们将原来的 n 个二元组按 val 值分类后,不同类的两个元素必本质不同,

而对于属于同一类的两个二元组 (a_i, b_i), (a_j, b_j),想要使这两者对应的新二元组本质相同,

我们只需要让 a'_i = a'_j 即可, 因为若 val_i = val_j,则 a'_i + b'_i = a'_j + b'_j

我们同时可以发现,对某个二元组 (a_i, b_i),由它构造出的新二元组 (a'_i, b'_i) 必满足以下条件:

以上五个条件是构造出的新二元组 (a'_i, b'_i) 合法的充要条件。

我们将上面五个条件合并,即可得出:

此时我们发现,当 $a'_i$ 固定后, $b'_i$ 也跟着固定,且所有满足条件的 $a'_i$ 构成一段区间。 此时,我们可以尝试从一种不同的角度来刻画这个问题: 我们构造一条数轴,数轴上有若干个点,我们可以在一定范围内挪动这个点, 而我们需要的,是让这些点尽可能的重合,并使最后不在同一个位置的点的数量最少。 我们发现,这些点的初始位置无关紧要,重要的只有其可以到达的范围。 同时,“使最后不在同一个位置的点的数量最少” 这个条件,可以用一种更简洁的方式描述: 我们直接在数轴上选取若干个位置,作为初始的每个点最后到达的位置集合, 那么我们需要做到的,就是让原先的每个点,在最后都有一个 "落脚点" 在位置集合中。 那么,我们就可以将原问题转化成以下问题: 给定 $n$ 段在数轴上的区间,要求放置若干个点,使得每段区间内都包含至少一个点, 请最小化放置的点数并给出方案。 这就是一个典型的区间覆盖问题了,我们可以用贪心来解决。 首先,对于两个区间 $[l_1, r_1], [l_2, r_2]$,若存在 $l_1 \leq l_2, r_1 \geq r_2$,即 $[l_2, r_2]\in [l_1, r_1]$, 则当区间 $[l_2, r_2]$ 中包含了至少一个点,那么区间 $[l_1, r_1]$ 也至少包含了一个点, 此时同时考虑区间 $[l_1, r_1]$ 和 $ [l_2, r_2]$ 是多余的,我们就可以删除区间 $[l_1, r_1]$。 故对于两个存在包含关系的区间,我们可以删除更大的那个。 于是,对剩下的区间按左端点升序排序后,其右端点必单调递增。 此时,我们只需要贪心的放点即可,具体来说,就是: 遍历每个区间并记录当前放置的最靠右的点,若该点落在当前区间内就不管, 否则新放一个点在当前区间的右端点,并更新当前放置的最靠右的点的位置。 这个的正确性可以用扰动法证明,就不在此赘述了。 复杂度瓶颈在于排序,是 $O(n \log n)$ 的。 **代码**: ``` #define int long long #define rep(i, a, b) for (int i = (a); i <= (b); i++) #define per(i, a, b) for (int i = (a); i >= (b); i--) const int N (2e5 + 10); int n, tot, x[N], rx[N], ry[N]; struct Node { int a, b, m, s, id; } d[N]; struct Line {int l, r, v, id;} a[N]; inline bool cmp (Node u, Node v) { return u.s < v.s; } inline bool cmp2 (Line u, Line v) { if (u.r ^ v.r) return u.r < v.r; return u.l < v.l; } inline int solve (int st, int en) { int ans = 0; tot = 0; rep (i, st, en) a[++tot] = (Line) {max(0ll, d[i].a - d[i].m), min(d[i].a, d[i].s)}, a[tot].v = d[i].a, a[tot].id = i; sort (a + 1, a + tot + 1, cmp2); int pos = -1e18; rep (i, 1, tot) { if (pos < a[i].l) ans++, pos = a[i].r; x[a[i].id] = a[i].v - pos; } return ans; } inline void work () { n = read(); rep (i, 1, n) { x[i] = rx[i] = ry[i] = 0; d[i].a = read(), d[i].b = read(), d[i].m = read(); d[i].s = d[i].a + d[i].b - d[i].m, d[i].id = i; } sort (d + 1, d + n + 1, cmp); int l = 0, r = 0, ans = 0; while (r <= n) { l = ++r; if (r > n) break ; while (r + 1 <= n && d[r + 1].s == d[l].s) ++r; ans += solve (l, r); } cout << ans << endl; rep (i, 1, n) rx[d[i].id] = x[i], ry[d[i].id] = d[i].m - x[i]; rep (i, 1, n) cout << rx[i] << ' ' << ry[i] << endl; } signed main() { int tasks = read(); while (tasks--) work(); return 0; } ```