题目链接

小红的排列构造①

题目描述

小红希望你构造一个长度为 的排列 ,使得对所有 ,都有 不是质数。

长度为 的排列是由 个正整数按任意顺序组成的数组,其中每个整数恰好出现一次。

解题思路

这是一个构造性问题。我们需要找到一个排列 ,使得所有的 都是合数。

一个关键的突破口是考虑 的奇偶性。如果一个数是大于 的偶数,那么它一定是合数。

  • 两个奇数之和是偶数。
  • 两个偶数之和是偶数。

这启发我们可以将排列中的数和它们的下标按照奇偶性进行配对。也就是说:

  • 如果下标 是奇数,我们就让 也为奇数。
  • 如果下标 是偶数,我们就让 也为偶数。

这样一来, 的结果就总是偶数。现在,我们只需要处理一种特殊情况:。这种情况会发生当且仅当 ,因为 都是正整数。如果 ,而 是质数,那么这个构造就失败了。

所以,我们的构造目标是:

  1. 奇数下标 对应奇数值
  2. 偶数下标 对应偶数值
  3. 必须避免

我们可以采用一个简单的构造方法:将所有偶数放在排列的前面,然后将所有奇数放在排列的后面。

具体来说,排列 构造为:

我们来验证这个构造是否满足条件:

  • 这是一个 的排列吗?是的,它包含了 的所有整数,每个恰好出现一次。
  • 对于每个 是合数吗?
    • 时:
      • :排列为 (质数),不满足。
      • :排列为 (质数),不满足。
    • 时:
      • 对于偶数部分 (其中 ):
        • 此时 是偶数,下标 可能是奇数或偶数。
        • (质数),这个构造方法在 时行不通。

看来简单的拼接不行,我们需要一个更精细的构造。回到奇偶配对的思路上,我们只需要保证 即可。一个简单的实现是,在各自的奇偶数集合内部进行循环移位。

  • 偶数部分:
  • 奇数部分:

这样构造,对于任意 的奇偶性都相同,所以 是偶数。同时,因为 时奇数至少有 两个,所以 会被赋值为 ,从而 ,这就保证了

总结一下最终的构造方案:

  • 时,不存在满足条件的排列,输出 -1。
  • 时:
    • (或 )中的偶数依次填入 。具体来说,就是
    • 然后将 (或 )中的奇数依次填入 。具体来说,也是
    • 这种构造等价于 for
    • 让我们验证一下这个更简单的构造:
      • For : 。我们需要保证 不是质数。例如当 , 是质数。所以这个简单构造也不行。

让我们回到最初最可靠的 奇偶数内部循环移位 构造方案。

  • 对于所有偶数下标 ,令 的下一个偶数。最后一个偶数下标对应的 值为第一个偶数
  • 对于所有奇数下标 ,令 的下一个奇数。最后一个奇数下标对应的 值为第一个奇数

这个方案可以保证 奇偶性相同,且当 ,因此 总是大于 的偶数,即合数。

代码

#include <iostream>
#include <vector>
#include <numeric>

using namespace std;

int main() {
    int n;
    cin >> n;

    if (n < 3) {
        cout << -1 << endl;
        return 0;
    }

    vector<int> p(n + 1);
    
    // 构造偶数部分的映射
    for (int i = 2; i <= n; i += 2) {
        if (i + 2 <= n) {
            p[i] = i + 2;
        }
    }
    int last_even = (n % 2 == 0) ? n : n - 1;
    p[last_even] = 2;

    // 构造奇数部分的映射
    for (int i = 1; i <= n; i += 2) {
        if (i + 2 <= n) {
            p[i] = i + 2;
        }
    }
    int last_odd = (n % 2 != 0) ? n : n - 1;
    p[last_odd] = 1;
    
    for (int i = 1; i <= n; ++i) {
        cout << p[i] << (i == n ? "" : " ");
    }
    cout << endl;

    return 0;
}
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();

        if (n < 3) {
            System.out.println(-1);
            return;
        }

        int[] p = new int[n + 1];
        
        // 构造偶数部分的映射
        for (int i = 2; i <= n; i += 2) {
            if (i + 2 <= n) {
                p[i] = i + 2;
            }
        }
        int lastEven = (n % 2 == 0) ? n : n - 1;
        p[lastEven] = 2;

        // 构造奇数部分的映射
        for (int i = 1; i <= n; i += 2) {
            if (i + 2 <= n) {
                p[i] = i + 2;
            }
        }
        int lastOdd = (n % 2 != 0) ? n : n - 1;
        p[lastOdd] = 1;

        for (int i = 1; i <= n; i++) {
            System.out.print(p[i] + (i == n ? "" : " "));
        }
        System.out.println();
    }
}
n = int(input())

if n < 3:
    print(-1)
else:
    p = [0] * (n + 1)
    
    # 构造偶数部分的映射
    i = 2
    while i <= n:
        if i + 2 <= n:
            p[i] = i + 2
        i += 2
    last_even = n if n % 2 == 0 else n - 1
    p[last_even] = 2
    
    # 构造奇数部分的映射
    i = 1
    while i <= n:
        if i + 2 <= n:
            p[i] = i + 2
        i += 2
    last_odd = n if n % 2 != 0 else n - 1
    p[last_odd] = 1
    
    result = []
    for i in range(1, n + 1):
        result.append(str(p[i]))
    print(" ".join(result))

算法及复杂度

  • 算法:数学、构造
  • 时间复杂度:,需要遍历两次(奇数和偶数)来填充排列数组。
  • 空间复杂度:,需要一个长度为 的数组来存储构造的排列。