题目链接
题目描述
小红希望你构造一个长度为 的排列
,使得对所有
,都有
不是质数。
长度为 的排列是由
这
个正整数按任意顺序组成的数组,其中每个整数恰好出现一次。
解题思路
这是一个构造性问题。我们需要找到一个排列 ,使得所有的
都是合数。
一个关键的突破口是考虑 的奇偶性。如果一个数是大于
的偶数,那么它一定是合数。
- 两个奇数之和是偶数。
- 两个偶数之和是偶数。
这启发我们可以将排列中的数和它们的下标按照奇偶性进行配对。也就是说:
- 如果下标
是奇数,我们就让
也为奇数。
- 如果下标
是偶数,我们就让
也为偶数。
这样一来, 的结果就总是偶数。现在,我们只需要处理一种特殊情况:
。这种情况会发生当且仅当
且
,因为
和
都是正整数。如果
,而
是质数,那么这个构造就失败了。
所以,我们的构造目标是:
- 奇数下标
对应奇数值
。
- 偶数下标
对应偶数值
。
- 必须避免
。
我们可以采用一个简单的构造方法:将所有偶数放在排列的前面,然后将所有奇数放在排列的后面。
具体来说,排列 构造为:
。
我们来验证这个构造是否满足条件:
- 这是一个
到
的排列吗?是的,它包含了
到
的所有整数,每个恰好出现一次。
- 对于每个
,
是合数吗?
- 当
时:
:排列为
。
(质数),不满足。
:排列为
。
(质数),不满足。
- 当
时:
- 对于偶数部分
(其中
):
- 此时
是偶数,下标
可能是奇数或偶数。
(质数),这个构造方法在
时行不通。
- 此时
- 对于偶数部分
- 当
看来简单的拼接不行,我们需要一个更精细的构造。回到奇偶配对的思路上,我们只需要保证 即可。一个简单的实现是,在各自的奇偶数集合内部进行循环移位。
- 偶数部分:
。
- 奇数部分:
。
这样构造,对于任意 ,
和
的奇偶性都相同,所以
是偶数。同时,因为
时奇数至少有
两个,所以
会被赋值为
,从而
,这就保证了
。
总结一下最终的构造方案:
- 当
时,不存在满足条件的排列,输出 -1。
- 当
时:
- 将
(或
)中的偶数依次填入
。具体来说,就是
。
- 然后将
(或
)中的奇数依次填入
。具体来说,也是
。
- 这种构造等价于
for
,
。
- 让我们验证一下这个更简单的构造:
- 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))
算法及复杂度
- 算法:数学、构造
- 时间复杂度:
,需要遍历两次(奇数和偶数)来填充排列数组。
- 空间复杂度:
,需要一个长度为
的数组来存储构造的排列。