数字拆分问题算法回溯_数字方阵2 题解 回溯算法

题目描述

在N*N的棋盘上(1<N≤10)填入1,2,...N*N共N*N个数,使得任意两个相邻的数之和为素数.例如,当N=2时,有 :

1

2

4

3

其相邻数的和为素数的有:1+2,1+4,4+3,2+3。

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

1

2

11

12

16

15

8

5

13

4

9

14

6

7

10

3

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

输入

一个正整数N。

输出

若有多种解,则需输出第一行之和最小,若第一行和相同,则输出第一列之和最小的排列方案;若无解,则输出"No solution"。若有解,第一行为空格分割的两个正整数,分别是该排列方案的第一行、第一列各数字之和。以下n行输出该排列方案,每个数字占4个字符,不足4位的在数字前用空格补齐。

样例输入

2

样例输出

3 5

1 2

4 3

算法分析

这题和洛谷上的不!一!样!!

输出格式:

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

洛谷上这题要求是:“如有多种解,则输出第一行、第一列之和为最小的排列方案”,只要搜到第一个解直接输出结束。但是这题不行,这题只能把所有解都搜过去。。。

要是爆搜肯定超时。。但是n小呀,于是开始了疯狂的预处理+剪枝。。。

因为要求输出第一行之和最小的排列方案,若第一行和相同,则输出第一列之和最小的排列方案。所以可以尝试先搜索第1行,接着再搜索第1列,最后再搜索中间那块。

初始化:

读入n,筛出2…2*n*n之间的所有素数表prime[i],用于表示i是否为素数。(线性筛,FindPrime())

对于每一个数i,找出2...2*n*n之内的所有能使i+j为素数的j,并储存起来。(predeal())

搜索第1行(workhang(int dx)):在搜索时可能判断当前已经搜索过的格子中数字之和hang是否大于先前答案中第一行的和ansh,如果是可不用继续搜索,这就是搜索剪枝。

搜索第1列(worklie(int dy)),同样,搜索列也可剪枝。

搜索中间部分(workmid(int dx,int dy)):只要已经出现过一个解,就不继续搜索,也可剪枝。而且竖着搜比横着搜快。(没有为什么。。数据就是这样。。。)

下面贴代码:

1 #include

2 #include

3 #include

4 #include

5 #define reg register

6 using namespacestd;7 int n,N;//N=n*n,即最大的数,方便以后遍历所有数(也省时间)

8 int map[11][11];//储存方阵

9 bool prime[20000], v[200];//prime[i]表示i是否为素数,v[i]表示i是否用过(用于搜索)

10

11 int pprime[20000];//用于筛素数

12 inline void FindPrime() { //筛素数,这里用的是线性筛,不懂可以去百度

13 int k=0, ls=2*N+5;14 memset(prime, 1, sizeof(prime));15 prime[1]=false;16 for (reg int i=1; i<=ls; ++i) {17 if (prime[i]) pprime[++k]=i;18 for (reg int j=1; j<=k; ++j) {19 prime[i*pprime[j]]=false;20 if (i%pprime[j]==0) break;21 }22 }23 }24 int pre[101][101];//存放预处理结果

25 inline void predeal(){//预处理

26 int k=2; //k:储存方案数

27 for(reg int i=1;i<=N;++i){28 k=2;29 for(reg int j=((i&1)?2:3);j<=N;j+=2){30 if(prime[j+i])31 pre[i][k++]=j;//将使i+j为素数的所有可能的j存入pre[i][2...k];

32 }33 pre[i][1]=k-2;//pre[i][1]存放方案数(j有几种可能),用于之后的遍历

34 }35 }36 int hang=1,lie=1;//hang:当前第一行所有数之和 lie:当前第一列所有数之和

37 int ansh = 1000, ansl = 1000,count=0;38 //ansh ansl:储存最优解(最小的hang lie);count:方案数(判断是否No solution)

39 int ansmap[11][11];//储存答案方阵

40 inline void print() {//简单的输出结果的函数

41 printf("%d %d\n", ansh, ansl);42 for (reg int i=1; i<=n; i++) {43 for (reg int j=1; j<=n; j++)44 printf("%4d", ansmap[i][j]);45 printf("\n");46 }47 }48 inline void update() {//更新结果(判断当前结果是否比上一个结果更优)

49 ansh = hang; ansl =lie;50 memcpy(ansmap, map, sizeof(map));51 count++;52 }53 bool found;//因为当第一行与第一列已经确定了时,中间这块对答案无影响,只要搜索出一个结果即可,用于剪枝

54 inline void workmid(int dx, intdy) {55 if(found)return;//只要中间这块已经被搜索出来了,即可停止

56 if((map[dx-1][dy]&1)^(map[dx][dy-1]&1))57 //若上面的数与左边的数一奇一偶,肯定不符合(i+left和i+up中必定有一个为偶数,非素数)

58 return;59 int left=map[dx-1][dy];//储存上面一格的数

60 for (reg int k = 2; k <= pre[left][1]+1; ++k) {61 int i=pre[left][k];//遍历使left+i为素数的所有i,后面写法与此相同

62 if (prime[i+map[dx][dy-1]] && !v[i]) {63 map[dx][dy]=i, v[i]=1;64 if (dx

65 else if (dx==n && dy

66 else if (dx==n && dy==n) update(), v[i]=0, found=1;//中间都搜完了,更新结果,标记found

67 }68 }69 }70 inline void worklie(int dy) {//搜索列

71 int up=map[dy-1][1];72 for (reg int k=2; k<=pre[up][1]+1; ++k) {73 int i=pre[up][k];74 if (!v[i] && (hang

82 int left=map[1][dx-1];83 for (reg int k=2; k<=pre[left][1]+1; ++k) {84 int i=pre[left][k];85 if (!v[i] && (hang+i)<=ansh){86 map[1][dx]=i, v[i]=1, hang+=i;87 if (dx==n) worklie(2), v[i]=0, hang-=i;88 else workhang(dx+1), v[i]=0, hang-=i;89 }90 }91 }92 intmain() {93 cin>>n; N=n*n;94 FindPrime();95 predeal();96 map[1][1] = 1;97 workhang(2);98 if(count>0)print(); else printf("No solution");99 return 0;100 }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值