题目背景
NOIP1997 提高组第一题
P5512 为本题数据加强版。
本题实际的数据的范围是 1≤N≤5。不保证存在可以通过原数据范围的非打表做法。
题目描述
在 N×N(1≤N≤10)的棋盘上,填入 1,2,…,N2 共 N2 个数,使得任意两个相邻的数之和为素数。
例如:当 N=2N=2 时,有:
11 | 22 |
---|---|
44 | 33 |
其相邻数的和为素数的有:
1+2,1+4,4+3,2+3
当 N=4N=4 时,一种可以填写的方案如下:
1 | 2 | 11 | 12 |
---|---|---|---|
16 | 15 | 8 | 5 |
13 | 4 | 9 | 14 |
6 | 7 | 10 | 3 |
在这里我们约定:左上角的格子里必须填数字 11。
输入格式
一个数 N。
输出格式
如有多种解,则输出第一行、第一列之和为最小的排列方案;若无解,则输出 NO
。
输入输出样例
输入 #1
1
输出 #1
NO
输入 #2
2
输出 #2
1 2 4 3
-
前言:作者这几天来心血来潮,决定把 NOIP−TG 的历届赛题给做一遍,只做难题,不做水题
-
作者写完了这道题,就来发题解
说了这么多,现在进入正题
题目大意分析:
- 生成一个全排列
- 判断全排列是否满足任意相邻的数和为质数
Step1:枚举
为了图省事,作者决定用 STLSTL 库的 nextpermutation函数
那么问题来了,nextpermutation函数只能适用与一维的全排列
对于本题来说,是不是不能用呢?
然而不是。我们可以把这个矩阵从二维缩成一维
用一张图来表示一下吧
原来的图:(保存两个值,x和y坐标)
1,1 | 1,2 | 1,3 |
---|---|---|
2,1 | 2,2 | 2,3 |
3,1 | 3,2 | 3,3 |
如果我们把这张图压缩到一维,就成了这样
1 | 2 | 3 |
---|---|---|
4 | 5 | 6 |
7 | 8 | 9 |
问题也简化了很多,我们也可以使用nextpermutation
while(next_permutation(a+1,a+1+n*n)) {
//To do
}
Step2:判质数
这道题貌似很卡时间,所以我们就要考虑优化时间复杂度
于是就可以使用筛法了
本人用的是埃式筛法,时间复杂度为:
生成O(n log n)
每次查询时O(1)
void make_prime(int x) {
p[1] = true ;
for(int i=2;i<=x;++i) {
if(p[i] == false) {
for(int j=2;j*i<=x;++j) {
p[i*j] = true ;
}
}
}
return ;
}
调用时只要判断p[x] == false
即
#define isPrime(x) p[x] == false
如果实在不会筛法的看这里,有O(sqrt(n))时间复杂度的质数判断
bool check() {
for(int i=1;i<=n*n-n;++i) {
if(! isPrime(a[i] + a[i + n])) {
return false;
}
}
for(int i=1;i<=n*n;++i) {
if(i % n == 0) {
continue ;
}
if(! isPrime(a[i] + a[i + 1])) {
return false ;
}
}
return a[1] == 1 ;
}
Step3:判断
传统方法:把每一个点的上下左右都判断一下
优化:只判断这个点的下和右
为什么这个优化是成立的呢?
原本在程序中,一对相邻的数会被判断两次,这样优化后,大大降低了程序的运行时间
bool check() {
for(int i=1;i<=n*n-n;++i) {
if(! isPrime(a[i] + a[i + n])) {
return false;
}
}
for(int i=1;i<=n*n;++i) {
if(i % n == 0) {
continue ;
}
if(! isPrime(a[i] + a[i + 1])) {
return false ;
}
}
return a[1] == 1 ;
}
STEP4综合程序(注意这份程序会TLE,去看STEP4)
#include<bits/stdc++.h>
#define isPrime(x) p[x] == false
using namespace std;
unsigned long long read() {
unsigned long long s=0,w=1;
char c=getchar();
while(c<'0'||c>'9') {if(c=='-') w=-1; c=getchar();}
while(c>='0'&&c<='9') s=s*10+c-'0',c=getchar();
return s*w;
}
const int MAXN = 20 * 20 ;
int a[MAXN],p[MAXN];
int n;
//bool isPrime(int x) {
// for(int i=2;i*i <= x ; ++i) {
// if(x % i == 0) {
// return false ;
// }
// }
// return x > 1 ;
//}
void make_prime(int x) {
p[1] = true ;
for(int i=2;i<=x;++i) {
if(p[i] == false) {
for(int j=2;j*i<=x;++j) {
p[i*j] = true ;
}
}
}
return ;
}
bool check() {
for(int i=1;i<=n*n-n;++i) {
if(! isPrime(a[i] + a[i + n])) {
return false;
}
}
for(int i=1;i<=n*n;++i) {
if(i % n == 0) {
continue ;
}
if(! isPrime(a[i] + a[i + 1])) {
return false ;
}
}
return a[1] == 1 ;
}
void Print() {
for(int i=1;i<=n*n;++i) {
printf("%d ",a[i]);
if(i % n == 0 ) {
putchar('\n');
}
}
return ;
}
int main() {
scanf("%d",&n);
make_prime(n*n);
for(int i=1;i<=n*n;++i) {
a[i] = i;
}
while(next_permutation(a+1,a+1+n*n)) {
if(check()) {
Print();
return 0;
}
}
printf("NO\n");
return 0;
}
Step5:???
当然,别急着提交,这份答案会TLE的
俗话说的好~
暴力出奇迹,打表过省一
在竞赛中,打表是一种很好的方法
并不是一无是处的
在答案的种数很少时就派上用场了,能够降低程序的运行时间
#include<bits/stdc++.h>
using namespace std;
int n;
int main() {
scanf("%d",&n);
if(n==1 || n==3 || n==9) {
printf("NO\n");
} else if(n==2) {
printf("1 2\n");
printf("4 3\n");
} else if(n==4) {
printf("1 2 11 12 \n");
printf("4 15 8 5 \n");
printf("7 16 3 14 \n");
printf("6 13 10 9 \n");
} else if(n==5) {
printf("1 2 3 4 7 \n");
printf("6 17 14 15 16 \n");
printf("13 24 5 8 21 \n");
printf("10 19 12 11 20 \n");
printf("9 22 25 18 23 \n");
} else if(n==6) {
printf("1 2 3 4 7 6 \n");
printf("10 27 34 25 36 35 \n");
printf("21 16 13 18 23 8 \n");
printf("22 31 30 29 14 5 \n");
printf("19 12 11 32 15 26 \n");
printf("24 17 20 9 28 33 \n");
} else if(n==7) {
printf("1 2 3 4 7 6 5 \n");
printf("10 27 44 39 40 31 48 \n");
printf("9 34 45 22 49 30 41 \n");
printf("8 33 28 25 12 29 38 \n");
printf("15 46 43 36 35 32 21 \n");
printf("16 13 18 11 26 47 20 \n");
printf("37 24 19 42 17 14 23 \n");
} else if(n==8) {
printf("1 2 3 4 7 6 5 8 \n");
printf("10 57 56 33 64 37 42 59 \n");
printf("9 52 51 50 63 46 61 48 \n");
printf("14 45 62 47 26 27 40 49 \n");
printf("17 44 35 54 53 20 39 58 \n");
printf("12 29 24 43 36 11 32 21 \n");
printf("55 18 19 28 31 30 41 38 \n");
printf("34 25 22 15 16 13 60 23 \n");
} else if(n==10) {
printf("1 2 3 4 7 6 5 8 9 10 \n");
printf("12 95 98 99 100 97 96 71 92 87 \n");
printf("11 86 93 80 57 94 85 78 89 62 \n");
printf("18 65 74 83 56 45 82 49 90 77 \n");
printf("13 84 53 54 47 26 81 58 91 72 \n");
printf("16 73 36 43 66 41 68 69 88 79 \n");
printf("15 64 67 70 61 48 59 44 63 34 \n");
printf("14 39 40 33 76 31 30 23 38 75 \n");
printf("17 20 27 46 55 42 37 60 29 32 \n");
printf("50 21 52 51 28 25 22 19 24 35 \n");
}
return 0;
}