CF 1348F Phoenix and Memory

这个问题可以重新表述为:
给你 \(n\) 个区间 \([a_i, b_i]\)\(1 \le a_i \le b_i \le n\)
这里,区间 \([l, r]\) 指的是整数集合 \(\\{l, l+1, \dots, r\\}\)
从第 \(i\) 个区间中取一个数 \(x_i\),也就是说要求 \(a_i\le x_i \le b_i\)

  1. 判断是否存在一种取数方案使得所取出的 \(n\) 个数两两不同,换言之,使得 \(x_1, x_2, \dots, x_n\) 恰是 \(1\)\(n\) 的一个排列?
  2. 若可能,再判断这样的取数方案是否唯一。若唯一,输出唯一的排列,否则输出任意两个排列。

问题 1

这是一个经典问题。有两个贪心做法:

贪心做法一

将区间按右端点从小到大排序,按此顺序遍历这些区间,在每个区间中取所能取的最小的那个数。举例言之,有三个区间 \([1, 1], [1, 3], [2, 2]\),排序后是 \([1, 1], [2, 2], [1, 3]\)。在 \([1,1]\) 中取 \(1\)\([2, 2]\) 中取 \(2\)\([1,3]\) 中取 \(3\)

贪心做法二

这个做法有 \(n\) 个步骤。
\(S\) 是一个 multiset,初始为空。

第一步:将左端点等于 \(1\) 的区间的右端点加到 \(S\) 中。若 \(S\) 里的最小值小于 \(1\),则无解,否则从 \(S\) 里的最小值对应的那个区间中选取 \(1\),并把最小值从 \(S\) 中删除。

第二步:将左端点等于 \(2\) 的区间的右端点加到 \(S\) 中。若 \(S\) 里的最小值小于 \(2\),则无解,否则从 \(S\) 里的最小值对应的那个区间中选取 \(2\),并把最小值从 \(S\) 中删除。

\(k\) 步:将左端点等于 \(k\) 的区间的右端点加到 \(S\) 中。若 \(S\) 里的最小值小于 \(k\),则无解,否则从 \(S\) 里的最小值对应的那个区间中选取 \(k\),并把最小值从 \(S\) 中删除。

此贪心算法的正确性证明:https://codeforces.ml/blog/entry/76555?#comment-613974

容易看出,multiset 可换成小顶堆。

问题 2

顺着上述贪心做法二的思路我们可以判断出取数的方案是否唯一:
在第 \(k\) 步中如果 \(S\) 中至少有两个元素,并且 \(S\) 中的最小值大于 \(k\)——设 \(S\) 中的最小值和次小值对应的区间编号分别为 \(i\)\(j\)——那么我们看能否从区间 \(j\) 中取 \(k\)。如何判断呢?
可以这样做:假设正常情况下从区间 \(j\) 取出的数是 \(x_j\),我们看一下 \(x_j\) 能否从区间 \(i\) 中取出。

此解法来自 https://codeforces.ml/blog/entry/76555?#comment-613946

正确性我不会证明。

参考实现:https://codeforces.ml/contest/1348/submission/81636889

扩展

如何求有多少个不同的排列?

posted @ 2020-05-28 00:18  Pat  阅读(166)  评论(0编辑  收藏  举报