CF1607H 题解
GaryH
·
·
题解
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 c 或 b \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 \leq a_i
-
a'_i \leq val_i
-
a'_i \geq 0
-
a'_i \geq a'_i - m_i
-
a'_i + b'_i = val_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;
}
```