F. 怎么写线性SPJ (2种解法)

打得比较着急,可能有奇怪的 typo

Update 1: 修复了把 4 处 min 打成 max 的 typo

Update 2: 修复了漏打了个 g 的 typo

Update 3: 修正了第 2 个解法的证明部分

本题解中数组 的下标从 1 开始.

记满足条件的 的最小"种类数"为 . 初值 . 下面考虑对于正整数 .

中一定有一个数只出现一次,设这个数的下标是 .

,这里

因为 的增加而单调不减([不严格]单调增加),所以,如果 为奇数,则

如果 为偶数,则

(这里

综上,

结合 和上面的递推公式,可得:对任意正整数 ,有 .

因此可以满足题目中 的要求.

因此我们可以取 ,来构造一个最优解.

那么,我们写一个 DFS,把这个过程模拟出来就可以了.

DFS 构造解法

#include <iostream>
#include <vector>

void solve() {
    int n;
    std::cin >> n;
    std::vector<int> p(n + 1, 0);
    int cnt = 0;
    auto dfs = [&](auto& self, int l, int r, int v) -> void {
        if (l > r) {
            return;
        }
        // n = r - l + 1
        // m = l - 1 + floor((n + 1) / 2) = l - 1 + floor((r - l + 2) / 2) = floor((l + r) / 2)
        int m = (l + r) / 2;
        p[m] = v;
        cnt = std::max(cnt, v);
        self(self, l, m - 1, v + 1);
        self(self, m + 1, r, v + 1);
    };
    dfs(dfs, 1, n, 1);
    std::cout << cnt << "\n";
    for (int i = 1; i <= n; ++i) {
        std::cout << p[i] << " \n"[i == n];
    }
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    int t = 1;
    // std::cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}

时间复杂度:.

空间复杂度:.

贪心构造解法

下面的代码来自于贪心构造. 显然它生成出的数组 的"种类数"为 ,已经满足最小了,且 ,只需要证明

数组的每一个非空连续子数组都存在至少一个“唯一元素”.

证明:只需要考察 是正整数)的情况. 这是因为,即使 是正整数)且 无法这样表示,也可以找到正整数 使得

(只需要让 足够大即可.)

根据代码的构造逻辑, 的数组是 的数组的一个前缀(这当然是它的一个非空连续子数组),如果 的情况得证,那么 的情况也得证了.

接下来,使用数学归纳法证明,对任意正整数 的数组的每一个非空连续子数组都存在至少一个“唯一元素”.

时,,对应的数组是 [1],它的非空连续子数组只有它本身, 是这个数组的一个"唯一元素",因此当 时命题成立.

假设当 是正整数)时命题成立,那么当 时,,它的数组可以由这 3 部分拼接而成:

​ 第 1 部分: 的数组

​ 第 2 部分:[m + 1]

​ 第 3 部分: 的数组

举个例子

时,,对应的数组是

[1, 2, 1, 3, 1, 2, 1, 4, 1, 2, 1, 3, 1, 2, 1, 5, 1, 2, 1, 3, 1, 2, 1, 4, 1, 2, 1, 3, 1, 2, 1]

它由下面部分组成:

第 1 部分:

[1, 2, 1, 3, 1, 2, 1, 4, 1, 2, 1, 3, 1, 2, 1]

第 2 部分:

[5]

第 3 部分:

[1, 2, 1, 3, 1, 2, 1, 4, 1, 2, 1, 3, 1, 2, 1]

因为当 时命题成立,所以第 1 部分和第 3 部分的非空连续子数组都存在至少一个"唯一元素".

而第 2 部分的非空连续子数组只有它本身,也存在一个"唯一元素" .

只需要考虑跨越多个部分的非空连续子数组. 而这样的非空连续子数组一定包含 ,注意, 的所有下标为

其中 是非负整数.

显然 不可能落在 上,也不可能落在 上,这是因为, 没有前一个下标,而后一个下标是

因此 是这个数组的一个"唯一元素".

因此当 时命题成立.

根据数学归纳法,原命题得证.

#include <iostream>
#include <vector>

void solve() {
    int n;
    std::cin >> n;
    std::vector<int> p(n + 1, 0);
    int v = 1, pos = 1, d = 2;
    while (pos <= n) {
        for (int k = pos; k <= n; k += d) {
            p[k] = v;
        }
        v = v + 1;
        pos = pos * 2;
        d = d * 2;
    }
    std::cout << (v - 1) << "\n";
    for (int i = 1; i <= n; ++i) {
        std::cout << p[i] << " \n"[i == n];
    }
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    int t = 1;
    // std::cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}

时间复杂度:.

空间复杂度:.

贪心构造解法和 DFS 构造解法得到的数组 不相同,但都符合题意.