《算法竞赛·快冲300题》每日一题:“质数拼图游戏”

算法竞赛·快冲300题》将于2024年出版,是《算法竞赛》的辅助练习册。
所有题目放在自建的OJ New Online Judge。
用C/C++、Java、Python三种语言给出代码,以中低档题为主,适合入门、进阶。

文章目录

  • 题目描述
  • 题解
  • C++代码
  • Java代码
  • Python代码

质数拼图游戏” ,链接: http://oj.ecustacm.cn/problem.php?id=1818

题目描述

【题目描述】 拼图游戏由一个3×3的棋盘和数字1-9组成。目标是达到以下最终状态:
1 2 3
4 5 6
7 8 9
每次如果相邻两个数字之和为质数,则可以进行交换。
相邻:上下左右四联通
给定一个棋盘初始状态,求到达最终状态的最短步数。
【输入格式】 第一行为正整数T,表示存在T组测试数据,1≤T≤50。
对于每组测试数据,输入3行,每行3个数字表示棋盘。
输入保证合法,棋盘中的9个数字仅为1-9。
**【输出格式】**对于每组测试数据输出一个整数表示答案。如果无法到达最终状态,输出-1。
【输入样例】

2
7 3 2
4 1 5
6 8 9
9 8 5
2 4 1
3 7 6

【输出样例】

6
-1

题解

   本题是典型的BFS最短路。把棋盘上的每种数字组合看成一个状态,求从初始态到最终态的最短路径的步数。
  对一次单独的“初始态到终止态”计算,做一次BFS的复杂度等于棋盘状态的数量,一共有9! = 362880状态,总数量并不多。但是题目有50个测试,总计算量约为50×362880,超时。
  本题需要做一个简单的转换。由于从任何初始态出发终止态都是固定的“1 2 3 4 5 6 7 8 9”,可以反过来,把终止态看成起点,把初始态看成终点,那么就是求一个固定起点到任意终点的最短路。只需做一次BFS,就能得到从起点到所有终点的最短路。对于T次测试,每个测试直接返回已经算出的结果即可。总计算量只是做一次BFS的9!。
  题目还需要判断相邻数的和是否为质数,虽然可以写一个函数判断质数,但是本题的两数和范围是3 ~ 17,非常少,只需用一个数组预存这些数字是否为质数即可。
  一个点的邻居是它上下左右的点,不过,只需考虑它的右边和下面的邻居即可。请思考原因。
  最后还有一个技巧:把二维的3×3棋盘化为一维的9个点,编程更简单。
【重点】 BFS,判重 。

C++代码

   BFS的队列需要判重,下面代码用map判重。

#include
using namespace std;
bool isprime[21] = {0,0,1,1,0,1,0,1,0,0,0,1,0,1,0,0,0,1,0,1,0}; //和为质数的情况
unordered_map<string, int> ans;   //unordered_map查询单个key时效率比map高
int dir[][2] = {1,0, 0,1};        //1,0是向右,0,1是向下
void bfs(string s){
    queue<string>q;
    q.push(s);
    ans[s] = 0;            //s到自己的步数为0
    while(!q.empty()) {
        string Now = q.front();
        q.pop();
        for(int i = 0; i <= 2; i++)          //遍历x方向的3个数
            for(int j = 0; j <= 2; j++)   {  //遍历y方向的3个数
                int one = i * 3 + j;         //把二维坐标(i,j)转化为一维
                for(int k = 0; k <= 1; k++) { //与右边交换,与下面交换
                    int nx = i + dir[k][0];
                    int ny = j + dir[k][1];
                    if(nx == 3 || ny == 3)  continue; //越界了
                    int two = nx * 3 + ny;
                    if(isprime[Now[one]-'0' + Now[two]-'0']) { //相邻数之和是质数
                        string Next = Now;          //
                        swap(Next[one], Next[two]); //交换相邻数
                        if(!ans.count(Next)) {           //ans是map,用map判重
                            ans[Next] = ans[Now] + 1;
                            q.push(Next);
                        }
                    }
                }
            }
    }
}
int main(){
    string s = "123456789";
    bfs(s);               //计算s到其它所有状态的最短路长度
    int i = 0;
    int T;  cin >> T;
    while(T--) {
        string now = "";
        for(int i = 1; i <= 3; i++)
            for(int j = 1; j <= 3; j++)  {
                char c;   cin >> c;
                now += c;
            }
            if(ans.count(now))  cout<<ans[now]<<endl;
            else                cout<<-1<<endl;
    }
    return 0;
}

Java代码

  BFS的队列需要判重,下面代码用map判重。

import java.util.*;
public class Main {
    static int isprime[] =  new int[]{0,0,1,1,0,1,0,1,0,0,0,1,0,1,0,0,0,1,0,1,0};
    static Map<String, Integer> ans = new HashMap<>();
    static int[][] dir = {{1,0}, {0,1}};
    public static void bfs(String s) {
        Queue<String> q = new LinkedList<>();
        q.offer(s);
        ans.put(s, 0);
        while (!q.isEmpty()) {
            String now = q.poll();
            for (int i = 0; i <= 2; i++) {
                for (int j = 0; j <= 2; j++) {
                    int one = i * 3 + j;
                    for (int k = 0; k <= 1; k++) {
                        int nx = i + dir[k][0];
                        int ny = j + dir[k][1];
                        if (nx == 3 || ny == 3) continue;
                        int two = nx * 3 + ny;
                        if (isprime[now.charAt(one) - '0' + now.charAt(two) - '0'] == 1) {
                            String next = now;
                            char[] chars = next.toCharArray();
                            char temp = chars[one];
                            chars[one] = chars[two];
                            chars[two] = temp;
                            next = new String(chars);
                            if (!ans.containsKey(next)) {
                                ans.put(next, ans.get(now) + 1);
                                q.offer(next);
                            }
                        }
                    }
                }
            }
        }
    }
    public static void main(String[] args) {
        String s = "123456789";
        bfs(s);
        Scanner scanner = new Scanner(System.in);
        int t = scanner.nextInt();
        while (t-- > 0) {
            String now = "";
            for (int i = 1; i <= 3; i++) {
                for (int j = 1; j <= 3; j++) {
                    char c = scanner.next().charAt(0);
                    now += c;
                }
            }
            if (ans.containsKey(now))   System.out.println(ans.get(now));
            else             System.out.println("-1");
        }
    }
}

Python代码

   BFS的队列需要判重,下面代码用字典判重。
  为了加快运行速度,下面的Python代码用了几个技术:(1)队列用deque,deque比Queue和list快很多;(2)字典的key用整数,比用字符串快。

from collections import deque
isprime = [0,0,1,1,0,1,0,1,0,0,0,1,0,1,0,0,0,1,0,1,0]
ans = {}           #答案存在字典中
dir = [[1, 0], [0, 1]]
F = [100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1]
def bfs(s):
    q = deque()
    q.append(s)
    ans[s] = 0
    while q:
        now = q.popleft()
        for i in range(3):
            for j in range(3):
                one = i * 3 + j                   #一个点,例如one=3,就是第2排第1个
                for k in range(2):
                    nx = i + dir[k][0]
                    ny = j + dir[k][1]
                    if nx == 3 or ny == 3:   continue
                    two = nx * 3 + ny             #它的邻居点:右边、下面
                    s_one = now // F[one] % 10    #例如 now=123456789,则s_one=6
                    s_two = now // F[two] % 10
                    if isprime[s_one + s_two]==1: #两数和是质数
                        next = now
                        next = next - s_one * F[one] + s_two * F[one]  #交换邻居点
                        next = next - s_two * F[two] + s_one * F[two]
                        if next not in ans:
                            ans[next] = ans[now] + 1
                            q.append(next)
s = 123456789
bfs(s) 
T = int(input())
for i in range(T):
    now = ''
    for j in range(3):   now += ''.join(input().split())
    now = int(now)                       #字典里面的key用整数比字符串快
    if now in ans:  print(ans[now])
    else:           print('-1') 

你可能感兴趣的:(算法竞赛快冲300题,算法)