[NOIP1997 提高组] 棋盘问题 题解

题目背景

NOIP1997 提高组第一题

P5512 为本题数据加强版。

本题实际的数据的范围是 1≤N≤5。不保证存在可以通过原数据范围的非打表做法。

题目描述

在 N×N(1≤N≤10)的棋盘上,填入 1,2,…,N2 共 N2 个数,使得任意两个相邻的数之和为素数。

例如:当 N=2N=2 时,有:

1122
4433

其相邻数的和为素数的有:

1+2,1+4,4+3,2+3

当 N=4N=4 时,一种可以填写的方案如下:

121112
161585
134914
67103

在这里我们约定:左上角的格子里必须填数字 11。

输入格式

一个数 N。

输出格式

如有多种解,则输出第一行、第一列之和为最小的排列方案;若无解,则输出 NO

输入输出样例

输入 #1

1

输出 #1

NO

输入 #2

2

输出 #2

1 2
4 3
  • 前言:作者这几天来心血来潮,决定把 NOIP−TG 的历届赛题给做一遍,只做难题,不做水题

  • 作者写完了这道题,就来发题解

说了这么多,现在进入正题

题目大意分析:

  1. 生成一个全排列
  2. 判断全排列是否满足任意相邻的数和为质数

Step1:枚举

为了图省事,作者决定用 STLSTL 库的 nextpermutation函数

那么问题来了,nextpermutation函数只能适用与一维的全排列

对于本题来说,是不是不能用呢?

然而不是。我们可以把这个矩阵从二维缩成一维

用一张图来表示一下吧

原来的图:(保存两个值,x和y坐标)

1,11,21,3
2,12,22,3
3,13,23,3

如果我们把这张图压缩到一维,就成了这样

123
456
789

问题也简化了很多,我们也可以使用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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值