《算法竞赛·快冲300题》将于2024年出版,是《算法竞赛》的辅助练习册。
所有题目放在自建的OJ New Online Judge。
用C/C++、Java、Python三种语言给出代码,以中低档题为主,适合入门、进阶。
【题目描述】 拼图游戏由一个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,判重 。
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;
}
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");
}
}
}
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')