puzzle(0111)《数独》

本文详细介绍了数独的基本规则、隐含规则及对称性,包括标准数独和斜线数独,并列举了多个数独求解的编程题目,如LeetCode的36和37题,POJ的3074和2676题,以及HDU的1426题。文章还讨论了解决数独的常用方法,如摒除法、鱼结构等,并分享了个人参与的湖南省首届高校联合数独大赛的经历。

目录

一,标准数独(规则数独)

1,规则

2,隐含规则

3,对称性

4,斜线数独

二,标准数独校验、求解

力扣 36. 有效的数独

POJ 3074 Sudoku

POJ 2676 Sudoku

HDU 1426 Sudoku Killer

力扣 37. 解数独

CSU 内部题 数独游戏(2017年院赛F题)

三,湖南省首届高校联合数独大赛

四,常用方法

1,摒除法

(1)摒除法

(2)宫内区块对行列摒除

(3)行列区块对宫摒除

(4)数对摒除

(5)三数组摒除

(6)显性数对

(7)数对占位

(8)显性三数组

(9)三元数组占位

(10)四数组摒除

(11)显性四数组

2,鱼结构

(1)X-Wing

(2)剑鱼

(3)水母

(4)多宝鱼

(5)空矩形

(6)2-String Kite

(7)摩天楼

3,外鳍

(1)外鳍 X-Wing

(2)外鳍剑鱼

(3)外鳍水母

(4)外鳍弗兰肯鱼

(5)外鳍退化 X-Wing

(6)外鳍退化剑鱼

(7)外鳍退化水母

4,唯一矩形

(1)可避免唯一矩形1型

(2)唯一矩形摒除

(3)唯一矩形2型

(4)唯一矩形4型

(5)唯一矩形6型

(6)唯一矩形1型

(7)可避免唯一矩形2型

(8)唯一矩形3型

(9)唯一矩形5型

5,else

(1)SDC

(2)W-Wing

(3)BUG+1

(4)XY-Wing

(5)XYZ-Wing

(6)ALS-XY-Wing

(7)X链

(8)ALS链

(9)远程数对

(10)XY链

(11)ALS-XZ链


一,标准数独(规则数独)

1,规则

数独盘面是个九宫,每一宫又分为九个小格。(宫即3*3的正方形)

在这八十一格中给出一定的已知数字,利用逻辑和推理,在其他的空格上填入1-9的数字。

使1-9每个数字在每一行、每一列和每一宫中都只出现一次,所以又称“九宫格”。

2,隐含规则

标准数独有一个隐含的规则,就是根据所给的数字,必须有且只有一个解

在这个前提下,可以证明,至少需要给出17个数字。

17个数字的数独也不难设计出来,下面的题目中就有。

3,对称性

规则数独,对称性自然只能指数字的位置分布了。

规则数独和不规则数独在数字的分布上其实差别不大,也是那几种对称性,后面给出了一个示例。

相对而言,规则数独要想给出对称的位置分布的初始局面,比较简单。

而且因为格子画的本身就很对称,所以整体看起来很对称,这一点,不规则数独一般都做不到。

还有斜线数独也有很强的对称性。

4,斜线数独

这个数独看起来给的数也不少了,有27个,但是很难。

想了半天,一个都推不出来,还好我有下面的代码。

输入:

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

输出:

293817654
761524893
854396127
138642975
475931286
629785431
587469312
916253748
342178569

这就是这个数独的解了。

二,标准数独校验、求解

力扣 36. 有效的数独

判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。

数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。


上图是一个部分填充的有效的数独。

数独部分空格内已填入了数字,空白格用 '.' 表示。

示例 1:

输入:
[
  ["5","3",".",".","7",".",".",".","."],
  ["6",".",".","1","9","5",".",".","."],
  [".","9","8",".",".",".",".","6","."],
  ["8",".",".",".","6",".",".",".","3"],
  ["4",".",".","8",".","3",".",".","1"],
  ["7",".",".",".","2",".",".",".","6"],
  [".","6",".",".",".",".","2","8","."],
  [".",".",".","4","1","9",".",".","5"],
  [".",".",".",".","8",".",".","7","9"]
]
输出: true
示例 2:

输入:
[
  ["8","3",".",".","7",".",".",".","."],
  ["6",".",".","1","9","5",".",".","."],
  [".","9","8",".",".",".",".","6","."],
  ["8",".",".",".","6",".",".",".","3"],
  ["4",".",".","8",".","3",".",".","1"],
  ["7",".",".",".","2",".",".",".","6"],
  [".","6",".",".",".",".","2","8","."],
  [".",".",".","4","1","9",".",".","5"],
  [".",".",".",".","8",".",".","7","9"]
]
输出: false
解释: 除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例1 相同。
     但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。
说明:

一个有效的数独(部分已被填充)不一定是可解的。
只需要根据以上规则,验证已经填入的数字是否有效即可。
给定数独序列只包含数字 1-9 和字符 '.' 。
给定数独永远是 9x9 形式的。

class Solution {
public:
    bool ok(vector<vector<char>>& board, int i, int j, int k)
    {
        for (int jj = 0; jj < 9; jj++)if (jj!=j && board[i][jj] == k)return false;
        for (int ii = 0; ii < 9; ii++)if (ii!=i && board[ii][j] == k)return false;
        int x = i / 3 * 3, y = j / 3 * 3;
        for (int ii = x; ii < x + 3; ii++)for (int jj = y; jj < y + 3; jj++)
            if ((ii!=i||jj!=j) && board[ii][jj] == k)return false;
        return true;
    }
    bool isValidSudoku(vector<vector<char>>& board) {
        for(int i=0;i<board.size();i++){
            for(int j=0;j<board[0].size();j++){
                if(board[i][j]!='.' && !ok(board,i,j,board[i][j])){
                    return false;
                }
            }
        }
        return true;
    }
};

POJ 3074 Sudoku

In the game of Sudoku, you are given a large 9 × 9 grid divided into smaller 3 × 3 subgrids. For example,

Given some of the numbers in the grid, your goal is to determine the remaining numbers such that the numbers 1 through 9 appear exactly once in (1) each of nine 3 × 3 subgrids, (2) each of the nine rows, and (3) each of the nine columns.

Input

The input test file will contain multiple cases. Each test case consists of a single line containing 81 characters, which represent the 81 squares of the Sudoku grid, given one row at a time. Each character is either a digit (from 1 to 9) or a period (used to indicate an unfilled square). You may assume that each puzzle in the input will have exactly one solution. The end-of-file is denoted by a single line containing the word “end”.

Output

For each test case, print a line representing the completed Sudoku puzzle.

Sample Input

.2738..1..1...6735.......293.5692.8...........6.1745.364.......9518...7..8..6534.
......52..8.4......3...9...5.1...6..2..7........3.....6...1..........7.4.......3.
end
Sample Output

527389416819426735436751829375692184194538267268174593643217958951843672782965341
416837529982465371735129468571298643293746185864351297647913852359682714128574936

这个题目,就是直接输入标准数独,输出数独的解。

题目里面有说You may assume that each puzzle in the input will have exactly one solution.

实际上这是标准数独必须满足的规定。

代码:

#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std;
 
int list[10][10];
bool ok(int i, int j, int k)
{
	for (int jj = 1; jj < 10; jj++)if (list[i][jj] == k)return false;
	for (int ii = 1; ii < 10; ii++)if (list[ii][j] == k)return false;
	int x = (i - 1) / 3 * 3, y = (j - 1) / 3 * 3;
	for (int ii = x + 1; ii <= x + 3; ii++)for (int jj = y + 1; jj <= y + 3; jj++)
		if (list[ii][jj] == k)return false;
	return true;
}
 
bool trys(int i, int j)
{
	if (j == 10)
	{
		i++;
		j = 1;
	}
	if (i == 10)return true;
	if (list[i][j])return trys(i, j + 1);
	for (int k = 1; k < 10; k++)
	{
		if (ok(i, j, k))
		{
			list[i][j] = k;
			if (trys(i, j + 1))return true;
		}
	}
	list[i][j] = 0;
	return false;
}
 
int main()
{
	char c,s[100];
	while (1)
	{
		scanf("%s", &s);
		if (s[0] == 'e')return 0;
		for (int i = 1; i < 10; i++)for (int j = 1; j < 10; j++)
		{
			c = s[i * 9 + j - 10];
			if (c == '.')list[i][j] = 0;
			else list[i][j] = c - '0';
		}
		trys(1, 1);
		for (int i = 1; i < 10; i++)for (int j = 1; j < 10; j++)printf("%d", list[i][j]);
		printf("\n");
	}
	return 0;
}

这个代码已经飞快了,解一个数独大概是几毫秒,然而还是超时了。

于是我用dancing links来做,AC了。

#include <iostream>
#include <vector>
#include <map>
#include <iomanip>
#include <string>
#include <algorithm>
#include <cmath>
#include <stack>
using namespace std;
class DancingLink
{
public:
	vector<int>rows;//覆盖选中的行,值的范围是从1到m
	DancingLink(int m, int n, int maxNum)
	{
		this->m = m, this->n = n;
		rhead.resize(m + 1), nums.resize(n + 1);
		row.resize(maxNum), col.resize(maxNum);
		up.resize(maxNum), down.resize(maxNum), lef.resize(maxNum), rig.resize(maxNum);
		sc.resize(m), rows.resize(m);
		for (int i = 0; i <= n; i++)
		{
			up[i] = i, down[i] = i;
			lef[i] = i - 1, rig[i] = i + 1;
			row[i] = 0, col[i] = i, nums[i] = 0;
		}
		lef[0] = n, rig[n] = 0, nums[0] = INT_MAX;
		scid = 0, rowsid = 0;
		key = n;
		for (int i = 0; i <= m; i++)rhead[i] = 0;
	}
	void push(int r, int c)//新增坐标在(r,c)的一个节点
	{
		row[++key] = r, col[key] = c;
		up[key] = c, down[key] = down[c];
		up[down[c]] = key, down[c] = key;
		if (rhead[r] == 0)rhead[r] = lef[key] = rig[key] = key;
		else
		{
			lef[key] = rhead[r], rig[key] = rig[rhead[r]];
			lef[rig[rhead[r]]] = key, rig[rhead[r]] = key;
		}
		nums[c]++;
	}
	bool dfs()
	{
		while (true) {
			if (rig[0] == 0) {
				rows.resize(rowsid);
				return true;
			}
			int c = min_element(nums.begin(), nums.end()) - nums.begin();
			del(c);
			while (c = down[c]) {
				if (c > n)break;
				reback(col[c]);
				c = sc[--scid];
				rowsid--;
				for (int j = rig[c]; j != c; j = rig[j])reback(col[j]);
			}
			sc[scid++]=c;//记录选中id
			rows[rowsid++]=row[c];
			for (int j = rig[c]; j != c; j = rig[j])del(col[j]);
		}
		return false;
	}
private:
	inline void del(int c)//删除第c列的所有元素和他们所在行的所有元素
	{
		lef[rig[c]] = lef[c], rig[lef[c]] = rig[c];
		for (int i = down[c]; i != c; i = down[i])
			for (int j = rig[i]; j != i; j = rig[j])
				down[up[j]] = down[j], up[down[j]] = up[j], nums[col[j]]--;
		nums[c] = INT_MAX;
	}
	inline void reback(int c)//完全回退del操作
	{
		lef[rig[c]] = rig[lef[c]] = c, nums[c] = 0;
		for (int i = down[c]; i != c; i = down[i]) {
			for (int j = rig[i]; j != i; j = rig[j])
				down[up[j]] = up[down[j]] = j, nums[col[j]]++;
			nums[c]++;
		}
	}
private:
	int m, n, key;
	vector<int>row, col;//每个节点的行,列
	vector<int>rhead;//每行第一个节点的id
	vector<int>up, down, lef, rig;//每个节点上下左右的节点id
	vector<int>nums;//每一列的元素个数
	vector<int>sc;
	int scid, rowsid;
};


string Sudoku(string s, char cEmpty = '.')
{
	int num = 0;
	for (int i = 0; i < 81; i++)if (s[i] != cEmpty)num++;
	int m = (81 - num) * 9 + num;
	int n = 81 * 4;
	DancingLink d(m, n, m * 4 + n + 5);
	int row = 0;
	map<int, int>mrow;
	mrow[0] = -1;
	for (int i = 0; i < 81; i++) {//第i个格子
		char c = s[i];
		int low = 0, high = 8;
		if (c != cEmpty)low = high = c - '1';//第i个格子的搜索值域
		for (int x = low; x <= high; x++) {
			d.push(++row, i + 1), d.push(row, i / 9 * 9 + x + 81 + 1);
			d.push(row, i % 9 * 9 + x + 162 + 1), d.push(row, (i / 27 * 3 + i % 9 / 3) * 9 + x + 243 + 1);
			mrow[row] = i;
		}
	}
	if (!d.dfs())return "";
	string ans = s;
	for (int i = 0; i < d.rows.size();i++) {
		int row = d.rows[i];
		int id = mrow[row];
		if (s[id] == cEmpty) {
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值