题目链接:https://www.yuque.com/mostshadowless/vx1lfh/ghwtxo#wTuap
题目分析:题目要求很明确,就是需要我们填充 5 * 5的矩阵,使其满足行、列、对角线均为素数,且每行、每列、主副对角线的数字和等于题目给定的值。难点在于如何把时间复杂度降下来,单纯的暴力搜索绝对不行。特别是需要我们去考虑如何在搜索的过程中剪枝。
解题步骤:
- 因为题目要求的是5位的素数,意味着其范围为 10000 ~ 99999,因为在搜索的过程中需要频繁的判断当前尝试的组合是否为素数,所以在进行搜索前,我们先预处理生成 100000 以内的素数,然后在搜索的过程中可以直接利用。在求解素数的过程中,还利用了埃式筛法加速[1, 100000]内素数的求解。
下面利用代码进一步展示细节。
//求解[1, N]的素数public static final int N = 100010;//判断当前数字 i 是否为素数public static boolean[] isPrime = new boolean[N];//若当前数字 i 为素数,存储其前后缀//例如 i = 11351 为素数//且有前缀为:1, 11, 113, 1135, 11351//后缀为1, 51, 351, 1351, 11351//之所以需要记录这些前缀,是因为利用前后缀进行剪枝,如果当前搜索得到的前后缀不是这些素数所拥有的前后缀,//必然是不符合条件的,直接剪枝public static boolean[] lSum = new boolean[N];public static boolean[] rSum = new boolean[N];//存储素数,以及素数的个数public static int cnt = 0;public static int [] prime = new int[N];//欧拉筛选素数public static void euler(){//初始化Arrays.fill(isPrime, true);isPrime[0] = false;for(int i = 2; i < N; i++){if(isPrime[i]){//记录这个素数prime[cnt++] = i;//计算当前素数各位置数字和int temp = i, t = 0;while(temp != 0){t = t + temp % 10;temp /= 10;}//同时处理所有前后缀的质数(必须是各位置数字和等于S的素数)if(i > 10000 && i < 100000 && t == S){lSum[i / 10000] = lSum[i / 1000] = lSum[i / 100] = lSum[i / 10] = lSum[i] = true;rSum[i % 10000] = rSum[i % 1000] = rSum[i % 100] = rSum[i % 10] = rSum[i] = true;}}//素数的倍数必然不是素数,提前排除for(int j = 0; j < cnt && i * prime[j] < N; j++){isPrime[i * prime[j]] = false;if(i % prime[j] == 0){break;}}}}
- 接下来就是进行搜索,搜索的顺序是从左往右,从上往下,简单的说,就是依次填充第一行的位置,然后填充第二行的位置,依次类推。
在搜索的过程中,需要记录当前行对应的数字,以及到当前搜索位置列的累加情况,分别对应代码中的 row[] 和 col[]。
同时,还有记录主副对角线的取值情况。
//存储素数矩阵public static int[][] arr = new int[5][5];//存储当前搜索位置累计的行的数字public static int[] row = new int[5];//存储当前搜索位置累计的列的数字public static int[] col = new int[5];//存储当前搜索位置主副对角线的位置public static int line1, line2;//权数组,为了方便副对角线的取值public static int[] pow = {1, 10, 100, 1000, 10000};//所有满足条件的方案数public static int total = 0;public static void dfs(int r, int c){if(c == 5){ //代表这一行已经搜索完成,转至下一行第一列开始搜索r = r + 1;c = 0;}if(r == 5){ //代表搜索完成,得到一种分配方案total++;if(total != 1){ //答案格式要求,每个方案空一行System.out.println();}for(int i = 0; i < 5; i++){ //输出结果System.out.println(row[i]);}return;}boolean[] flag = new boolean[4];for(int i = 0; i < 10; i++){//当前行flag[0] = lSum[row[r] * 10 + i];//当前列flag[1] = lSum[col[c] * 10 + i];//当前正对角线flag[2] = (r != c) || (r == c && lSum[line1 * 10 + i]);//当前副对角线flag[3] = (r + c != 4) || (r + c == 4 && rSum[line2 + i * pow[r]]);//如果上述四个条件有任意一个不满足,则剪枝if(!(flag[0] && flag[1] && flag[2] && flag[3])){continue;}//放置irow[r] = row[r] * 10 + i;col[c] = col[c] * 10 + i;if(r == c){ //主对角线line1 = line1 * 10 + i;}if(r + c == 4){ //副对角线line2 = line2 + i * pow[r];}dfs(r, c + 1); //搜索当前行的下一列//恢复irow[r] /= 10;col[c] /= 10;if(r == c){line1 /= 10;}if(r + c == 4){line2 %= pow[r];}}}
完整代码
import java.lang.reflect.Array;
import java.util.*;
public class Test{
public static final int N = 100010;
public static boolean[] isPrime = new boolean[N];
public static boolean[] lSum = new boolean[N];
public static boolean[] rSum = new boolean[N];
//存储素数,以及素数的个数
public static int cnt = 0;
public static int [] prime = new int[N];
//S:每行,每列,主副对角线元素之和需要等于S
//A:左上角元素取值(题目给定)
public static int S, A;
//存储素数矩阵
public static int[][] arr = new int[5][5];
public static int[] row = new int[5];
public static int[] col = new int[5];
public static int line1, line2;
public static int[] pow = {1, 10, 100, 1000, 10000};
//所有满足条件的方案数
public static int total = 0;
//欧拉筛选素数
public static void euler(){
Arrays.fill(isPrime, true);
isPrime[0] = false;
for(int i = 2; i < N; i++){
if(isPrime[i]){
prime[cnt++] = i;
int temp = i, t = 0;
while(temp != 0){
t = t + temp % 10;
temp /= 10;
}
//同时处理所有前后缀的质数
if(i > 10000 && i < 100000 && t == S){
lSum[i / 10000] = lSum[i / 1000] = lSum[i / 100] = lSum[i / 10] = lSum[i] = true;
rSum[i % 10000] = rSum[i % 1000] = rSum[i % 100] = rSum[i % 10] = rSum[i] = true;
}
}
for(int j = 0; j < cnt && i * prime[j] < N; j++){
isPrime[i * prime[j]] = false;
if(i % prime[j] == 0){
break;
}
}
}
}
public static void dfs(int r, int c){
if(c == 5){
r = r + 1;
c = 0;
}
if(r == 5){
total++;
if(total != 1){
System.out.println();
}
for(int i = 0; i < 5; i++){
System.out.println(row[i]);
}
return;
}
boolean[] flag = new boolean[4];
for(int i = 0; i < 10; i++){
//当前行
flag[0] = lSum[row[r] * 10 + i];
//当前列
flag[1] = lSum[col[c] * 10 + i];
//当前正对角线
flag[2] = (r != c) || (r == c && lSum[line1 * 10 + i]);
//当前副对角线
flag[3] = (r + c != 4) || (r + c == 4 && rSum[line2 + i * pow[r]]);
//如果上述四个条件有任意一个不满足,则剪枝
if(!(flag[0] && flag[1] && flag[2] && flag[3])){
continue;
}
//放置i
row[r] = row[r] * 10 + i;
col[c] = col[c] * 10 + i;
if(r == c){
line1 = line1 * 10 + i;
}
if(r + c == 4){
line2 = line2 + i * pow[r];
}
dfs(r, c + 1);
//恢复i
row[r] /= 10;
col[c] /= 10;
if(r == c){
line1 /= 10;
}
if(r + c == 4){
line2 %= pow[r];
}
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
S = sc.nextInt();
A = sc.nextInt();
euler();
for(int i = 0; i < 5; i++){
Arrays.fill(arr[i], -1);
}
arr[0][0] = A;
row[0] = col[0] = line1 = A;
dfs(0, 1);
if(total == 0){
System.out.println("NONE");
}
}
}
