1. 程式人生 > >數字方陣2 題解 回溯算法.md

數字方陣2 題解 回溯算法.md

判斷 所有 部分 不用 als 最優解 idt 去百度 要求

http://218.5.5.242:9018/JudgeOnline/problem.php?id=1263

題目描述

在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

算法分析

這題和洛谷上的不!一!樣!!

P1549 棋盤問題(2)(https://www.luogu.org/problemnew/show/P1549)

輸出格式:

如有多種解,則輸出第一行、第一列之和為最小的排列方案;若無解,則輸出“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 <iostream>
  2 #include <cstdio>
  3 #include <cmath>
  4 #include <cstring>
  5 #define reg register
  6 using namespace std;
  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, int dy) {
 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<n && dy<=n)workmid(dx+1, dy), v[i]=0;//往下搜一個 
 65             else if (dx==n && dy<n) workmid(2, dy+1), v[i]=0;//這一列搜完了,跳到下一列 
 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<ansh||(hang==ansh&&(lie+i)<ansl))){
 75             map[dy][1]=i, v[i]=1, lie+=i;
 76             if (dy==n)workmid(2, 2), v[i]=0, lie-=i, found=0;
 77             else worklie(dy+1), v[i]=0, lie-=i;
 78         }
 79     }
 80 }
 81 inline void workhang(int dx) {//搜索行 
 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 int main() {
 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 }

數字方陣2 題解 回溯算法.md