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 构造解法得到的数组 不相同,但都符合题意.