POJ 3120 Sudoku | BZOJ 2910 数独

题目:
http://poj.org/problem?id=3120
https://www.lydsy.com/JudgeOnline/problem.php?id=2910

题意:

数独是一个 9 × 9 9 \times 9 9×9 的棋盘,每个格子里可以填上 1 1 1 9 9 9 之间的整数,它可以被分成 9 9 9 3 × 3 3 \times 3 3×3 的格子,我们称其为九宫格。一个合法的数独局面满足每一行、每一列、每一个宫格的数字都不重复。

给定一个已经解好的数独局面,和一个还没解决的数独局面(一些该填数字的位置被留空),保证还没解决的局面有且仅有唯一解,问这个已经解好的数独局面能否通过有限次下述操作变换成后者对应的唯一解局面,操作包括

  • 9 × 9 9 \times 9 9×9 局面顺时针或逆时针旋转 90 90 90 度;
  • 将同一行的三个宫格的内部某两行依次交换;
  • 将同一列的三个宫格的内部某两列依次交换;
  • 将某两行的宫格依次整体交换;
  • 将某两列的宫格依次整体交换;
  • 选定一个双射 f : { 1 , 2 , … , 9 } → { 1 , 2 , … , 9 } f : \lbrace 1, 2, \ldots, 9 \rbrace \to \lbrace 1, 2, \ldots, 9 \rbrace f:{1,2,,9}{1,2,,9},将所有数字 x x x ( x = 1 , 2 , … , 9 x = 1, 2, \ldots, 9 x=1,2,,9) 用 f ( x ) f(x) f(x) 依次替换。

不超过 50 50 50 组测试数据。

题解:

注意到旋转操作总可以变换到最前面操作,因为对旋转 90 90 90 度后的行操作等价于对旋转 90 90 90 度前的列操作。同理,双射操作也总可以变换到最后面操作。

一个想法是直接搜出唯一解,然后尝试操作。由于假设了两个棋盘的所有位置都是满的,只要能确定左上角宫格的数字就能确定双射。那么只要能确定是否旋转、宫格行列的交换、左上角宫格内部行列的交换情况,即可确定双射,并依次确定每个宫格里是否存在唯一的行列交换情况能够满足双射。这样做的复杂度是 O ( S + 4 × ( 3 ! ) 4 × 9 2 ) \mathcal{O}(S+4 \times (3!)^4 \times 9^2) O(S+4×(3!)4×92),主要瓶颈在于搜出唯一解的代价 S S S

由于搜出唯一解的代价通常较高,不妨直接搜索旋转与行列变换情况,反而可能更好地利用到解唯一的性质。我的做法首先枚举旋转、宫格行列的变换情况,然后依次确定每个宫格内部的行列交换情况,如果在中途出现使双射无法建立的情况,则进行可行性剪枝。

不难计算出或注意到,题面中除双射外对应的操作只有 2 × ( 3 ! ) 8 2 \times (3!)^8 2×(3!)8 种可能的置换,这本质上是因为旋转 180 180 180 度的操作等价于进行一系列行列变换操作。利用这一点,可以将潜在的枚举量省去一半。

时间复杂度 O ( 2 × ( 3 ! ) 8 × 3 × 9 ) \mathcal{O}(2 \times (3!)^8 \times 3 \times 9) O(2×(3!)8×3×9),剪枝后非常快。

代码:

#include 
#include 
#include 
using namespace std;
int s[9][9], t[9][9], row[9], col[9], perm[11][19];
bool dfs(int idx) {
	if(idx == 9)
		return 1;
	int *cur = perm[idx], *nxt = perm[idx + 1];
	int bx = idx / 3, by = idx % 3;
	int dx = bx * 3, dy = by * 3, xL, xR, yL, yR;
	if(by) {
		xL = xR = 0;
	} else {
		xL = dx;
		xR = xL + 3;
		for(int i = xL; i < xR; ++i)
			row[i] = i;
	}
	do {
		if(bx) {
			yL = yR = 0;
		} else {
			yL = dy;
			yR = yL + 3;
			for(int i = yL; i < yR; ++i)
				col[i] = i;
		}
		do {
			bool chk = 1;
			memcpy(nxt, cur, sizeof perm[0]);
			for(int i = 0; chk && i < 3; ++i)
				for(int j = 0; chk && j < 3; ++j) {
					int u = s[row[dx + i]][col[dy + j]];
					int v = t[dx + i][dy + j];
					if(v == -1)
						continue;
					if(nxt[u] == -1) {
						nxt[u] = v;
					} else {
						chk &= nxt[u] == v;
					}
					if(nxt[9 + v] == -1) {
						nxt[9 + v] = u;
					} else {
						chk &= nxt[9 + v] == u;
					}
				}
			if(!chk)
				continue;
			if(dfs(idx + 1))
				return 1;
		} while(next_permutation(col + yL, col + yR));
	} while(next_permutation(row + xL, row + xR));
	return 0;
}
bool check() {
	int mat[9][9], row[3], col[3];
	memcpy(mat, s, sizeof mat);
	for(int i = 0; i < 3; ++i)
		row[i] = col[i] = i;
	memset(perm[0], -1, sizeof perm[0]);
	do {
		do {
			for(int i = 0; i < 9; ++i)
				for(int j = 0; j < 9; ++j)
					s[i][j] = mat[3 * row[i / 3] + i % 3][3 * col[j / 3] + j % 3];
			if(dfs(0))
				return 1;
		} while(next_permutation(col, col + 3));
	} while(next_permutation(row, row + 3));
	return 0;
}
int main() {
	int T;
	scanf("%d", &T);
	while(T--) {
		for(int i = 0; i < 9; ++i)
			for(int j = 0; j < 9; ++j) {
				scanf("%1d", s[i] + j);
				--s[i][j];
			}
		for(int i = 0; i < 9; ++i)
			for(int j = 0; j < 9; ++j) {
				scanf("%1d", t[i] + j);
				--t[i][j];
			}
		if(check()) {
			puts("Yes");
		} else {
			for(int i = 0; i < 9; ++i)
				for(int j = i + 1; j < 9; ++j)
					swap(s[i][j], s[j][i]);
			puts(check() ? "Yes" : "No");
		}
	}
	return 0;
}

你可能感兴趣的:(POJ,BZOJ,搜索,数独)