题解:P11105 [ROI 2023 Day 1] 解密(数据有误)

· · 题解

心路历程

我:令 R=\{x+1|x\in T\},也就是将所有数加一。
出题人:不行,T=\{1,2,3\}
我:将连续的数视为整体,这里令 R=\{4,5,6\}
出题人:不行,T=\{1,2,4,6\}
我:将所有数视为整体,有空位就往后拓展。这里令 R=\{3,5,7,8\}
出题人:不行,T=\{1,2,4,6,12\}
我:将 T 拆分为若干个集合,比如 T 拆分为 \{1,2,4,6\}\{12\},分别加密取并集。这里令 R=\{3,5,7,8,13\}
出题人:不行,n=12
我:将值域视作环形,令 12 的后继为 1R=\{3,5,7,8,9\}
出题人:……

正确解法

具体地,我们记一个 c 表示待拓展的数的个数,初始 c=0。从小到大遍历每一个数,每次将 c 增加 1 并尝试往后拓展,每拓展一个数 c 减去 1,直到遇到已出现的数或 c 变为 0 为止。

这样的话,每次 c 变为 0 等价于我们完成了一次拆分与加密。

解密就是往前拓展,不过你可以将每个 a_i 变为 n+1-a_i 然后和加密一样做。

一个细节:形如 n=10,T=\{1,10\} 一类的集合,如果你从小到大遍历,先划分出 \{1\} 并在 R 中加入 2,遍历到 10 时,需要跳过 12 直接在 R 中加入 3。具体见代码。

结论证明(过程极为繁琐)

如何证明加解密的正确性?我们具体考虑“往后拓展”的本质。以下探讨的 S \subseteq T 都是 T 中相邻的数构成的子集。

代码实现

代码细节很多;为了防止 xxs,本人的代码按照 QOJ 上的输入输出格式编写。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 6e5 + 3;
int a[maxn], b[maxn], n, k;
map <int, int> mp;
int main() {
    int id, T; cin >> id >> T;
    while (T--) {
        cin >> n >> k;
        for (int i = 1; i <= k; i++) cin >> a[i];
        if (id == 2) for (int i = 1; i <= k; i++) a[i] = n + 1 - a[i];
        sort(a + 1, a + k + 1);
        for (int i = 1; i <= k; i++) mp[a[i]] = 1;
        for (int i = 1, c = 0, j; i <= k; i++) {
            c++, j = a[i] + 1;
            mp[a[i]] = 3;
            if (j == n + 1) j = 1;
            while (mp[j] != 1 && c) { 
                if (!mp[j]) mp[j] = 2, c--;
                j++;
                if (j == n + 1) j = 1;
            }
            // j 在 map 里出现过,有两种可能:
            // 第一种可能:mp[j]==1,意味着碰到了另一个 T 中的数
            // 第二种可能:mp[j]==2或3,值域为环形,在例子中 n==10,T=={1,10} 的情况
            // 如 mp[1]=3 表示这个数时 T 中的数要 continue
            // mp[2]=2 表示这个数时 R 中的数要 continue
            // 注意不会遇到 需要break不需要continue 的 R 中的数
        }
        int tot = 0;
        for (auto p : mp) {
            if (p.second == 2) {
                if (id == 1) b[++tot] = p.first;
                else b[++tot] = n + 1 - p.first;
            }
        }
        sort(b + 1, b + tot + 1);
        for (int i = 1; i <= tot; i++) cout << b[i] << ' ';
        cout << '\n';
        mp.clear();
    } 
}

以下是一个真伪未知的证明(证明所有极小集合满足 t+2m-1\notin S)。在此求助大家,该证明过程是否正确?

若不然,则考虑对 S'=S\setminus \{t+2m-1\} 在忽略 t+2m-1 \in T 的前提下加密,不难证明加密出的结果 K' 仍满足 t+2m-1 \notin K'。即 S' 符合条件。由于在遍历到 S 前会遍历到 S',于是 S 不为极小集合。

最后,如果本文有不足之处,或者各位有更简洁的证明方法,可以在评论区指出!

“所以你写这么多为啥不感性理解呢?”
“还不是被某道名字是吻秋的题撤一堆题解给吓的。”