题解:CF2123F Minimize Fixed Points

· · 题解

前置知识、符号与约定

前置知识

符号与约定

文中用到的非公认或非常用的符号,按出现顺序排列:

题意

给定 n,构造一个 [n] 上的置换 p,满足:

  1. 最小化 \sum_{i=1}^{n} [p(i) = i]

报告任意一个 p。可以证明,p 总是存在的。

思路

观察 1:\sum_{i=1}^{n} [p(i) = i] 的下界

注意到

\forall\, i \in \{1\} \cup \mathbb{P} \cap \left[\left\lceil \frac{n}{2} \right\rceil, n\right], \quad p(i) = i

无法避免的,因此这些位置已经被固定

证明

而 $$ \forall\, i \in \mathbb{P} \cap \left[\left\lceil \frac{n}{2} \right\rceil, n\right], \quad p(i) = i $$ **无法避免**是因为 $$ \forall\, i \in \mathbb{P} \cap \left[\left\lceil \frac{n}{2} \right\rceil,\, n\right],\quad \forall\, j \in [n] \setminus \{i\},\quad i \perp j$$ 因为**最小**的,**不等于** $x$ 的,与 $x$ **不互质**的正整数是 $2x$。 因此, $\sum_{i=1}^{n} [p(i) = i]$ 的下界为 $\left|\{1\} \cup \mathbb{P} \cap \left[\left\lceil \frac{n}{2} \right\rceil, n\right]\right|$。 ## 转换 1:最小化 $\sum_{i= 1}^{n} [p(i) = i]$ 的另一种表示 设 $p$ 的轮换分解为 $C_1 C_2 \dots C_k$。 若存在 $p$ 的轮换 $\sigma$ 满足 $\gcd(\operatorname{supp}(\sigma)) > 1$ 且 $|\sigma| > 1$,则有 $$ \forall\, x \in \operatorname{supp}(\sigma),\quad p(x) \not\perp x\ \land\ p(x) \ne x $$ 因此在 $$ \forall\, i \in [n] \setminus \{1\}, \quad p(i) \not\perp i $$ 的前提下,最小化 $$ \sum_{i= 1}^{n} [p(i) = i] $$ 可转换为,在 $$ \forall\, i \in [k], \quad \gcd(\operatorname{supp}(C_i)) > 1 $$ 的前提下最小化 $$ \sum_{i= 1}^{k} \left[\left|C_i\right| = 1\right] $$ ## 观察 2:下界可以被取到 ### 证明 若有 $$ \forall\, i \in [n] \setminus \left(\{1\} \cup \mathbb{P} \cap \left[\left\lceil \frac{n}{2} \right\rceil, n\right]\right), \ \text{包含}\ i \ \text{的轮换} \ \sigma \ \text{满足} \ \gcd(\operatorname{supp}(\sigma)) > 1 \ \land \ |\sigma| > 1 $$ 则有下界可以被取到。 我们通过直接构造 $p$ 来证明: $$ p = D_1 \circ \left(\prod_{i \in \mathbb{P} \cap [2, n]} D_i\right) $$ 其中: - $D_1 = (1)$ 为平凡轮换 - $\forall \, i \in \mathbb{P} \cap [2,n]$,$D_i$ 为满足 $\operatorname{supp}(D_i) = \{ x \in [2,n] \mid P(x) =i \}$ 的轮换 因为有 $$ \forall\, i \in [n] \setminus \left(\{1\} \cup \mathbb{P} \cap \left[\left\lceil \frac{n}{2} \right\rceil, n\right]\right), \ \text{包含}\ i \ \text{的轮换} \ \sigma \ \text{满足} \ \gcd(\operatorname{supp}(\sigma)) \in \mathbb{P} $$ 所以有 $$ \forall\, i \in [n] \setminus \left(\{1\} \cup \mathbb{P} \cap \left[\left\lceil \frac{n}{2} \right\rceil, n\right]\right), \ \text{包含}\ i \ \text{的轮换} \ \sigma \ \text{满足} \ \gcd(\operatorname{supp}(\sigma)) > 1 $$ 仍需证明 $$ \forall\, i \in [n] \setminus \left(\{1\} \cup \mathbb{P} \cap \left[\left\lceil \frac{n}{2} \right\rceil, n\right]\right), \ \text{包含}\ i \ \text{的轮换} \ \sigma \ \text{满足} \ |\sigma| > 1 $$ 注意到有 $$ \forall\, i \in \mathbb{P} \cap \left[2, \left\lceil\frac{n}{2}\right\rceil\right ), \quad 2i \in [n] \ \land \ P(2i) = i $$ 综上所述,$p$ 符合题意要求,证毕。 # 算法&实现 ## 算法 上述思路中,我们需要实现,将每个数按最大质因子“归类”。 对每个数因数分解以寻找其最大质因子,不是明智的做法。因为寻找因子是**困难**的,而寻找倍数是**简单**的。 以下伪代码展示了如何预处理每个 $i \in [n]$ 的最大质因子。 ``` P[1] = P[2] = ... = P[n] = 0 for (i : 从大到小遍历 [1, n] 以内质数) { for (j = i; j <= n; j += i) if (P[j] == 0) { P[j] = i; } } ``` ```P[1], P[2], ..., P[n]``` 即为所求,时间复杂度 $\Theta(n \log \log n)$,空间复杂度 $\Theta(n)$。 ## 代码 ```cpp #include <bits/stdc++.h> using namespace std; constexpr int N = 100000 + 1; bitset<N> np; vector<int> primes; int p[N], cyc[N]; void solve() { int n; cin >> n; for (auto itr = lower_bound(primes.begin(), primes.end(), (n + 1) >> 1, greater<>()); itr != primes.end(); ++itr) { int i = *itr, len = 0; for (int j = i; j <= n; j += i) if (!p[j]) { cyc[len++] = j; } if (len) { for (int j = 0; j < len; ++j) { p[cyc[j]] = cyc[j + 1]; } p[cyc[len - 1]] = cyc[0]; } } for (int i = 1; i <= n; ++i) { cout << (p[i] ? p[i] : i) << ' '; } cout << '\n'; memset(p + 1, 0, sizeof(int) * n); } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); for (int i = 2; i * i < N; ++i) if (!np[i]) { for (int j = i * i; j < N; j += i) { np[j] = true; } } for (int i = 2; i < N; ++i) if (!np[i]) { primes.push_back(i); } reverse(primes.begin(), primes.end()); int t; cin >> t; while (t--) { solve(); } return 0; } ``` ## 复杂度分析 设 $N$ 为 $O(\max_{1 \leq i \leq t}{n_i})$。 上述代码时间复杂度为 $\Theta(N \log \log N + t \log \frac{N}{\log N} + \sum_{i=1}^t n_i \log \log n_i)$,空间复杂度为 $\Theta(N)$。 注意,若 ```cpp for (auto itr = lower_bound(primes.begin(), primes.end(), (n + 1) >> 1, greater<>()); itr != primes.end(); ++itr) ``` 写成 ```cpp for (auto itr = primes.begin(); itr != primes.end(); ++itr) ``` 则时间复杂度退化成 $\Theta(N \log \log N + t\frac{N}{\log N} + \sum_{i=1}^t n_i \log \log n_i)$。