Lista enlazada circular bidireccional, enlaces danzantes

Tabla de contenido

Lista enlazada circular bidireccional

Likou 426. Convierta un árbol de búsqueda binario en una lista doblemente enlazada ordenada

Lista enlazada circular bidireccional cruzada (enlaces danzantes)

Problema de cobertura precisa

Algoritmo X (versión recursiva V1)

POJ 3740 Búsqueda fácil

Sudokus

Optimización del algoritmo X

Algoritmo X (versión no recursiva V2)

Algoritmo X (versión no recursiva V3)

Algoritmo X (versión recursiva V4)

Algoritmo X (versión no recursiva V5)

Aceleración del algoritmo X (versión no recursiva V6)

Algoritmo X (versión no recursiva V7 basada en recursividad de cola)

Algoritmo X (versión final V8)

Aplicación del algoritmo X.

Problema de cobertura de empalme

Cobertura de empalme de bloques bidimensionales no repetitivos

Superposición de empalme de bloques repetidos 3D

problema de coincidencia

belleza de la simetría

Algoritmo de búsqueda de números agrupados

Sudoku estándar

Sudokus irregulares

Algoritmo de búsqueda de números de suma agrupada

Cobertura completa de Killer Sudoku

Sudoku asesino sin cobertura completa


Lista enlazada circular bidireccional

Likou 426. Convierta un árbol de búsqueda binario en una lista doblemente enlazada ordenada

Convierta un árbol de búsqueda binario en una lista enlazada doblemente circular ordenada in situ.

Para una lista circular bidireccional, puede utilizar los punteros secundarios izquierdo y derecho como punteros predecesores y sucesores de la lista enlazada circular bidireccional. El predecesor del primer nodo es el último nodo y el sucesor del último nodo es el primer nodo.

En particular, queremos poder realizar operaciones de conversión in situ. Cuando se completa la conversión, el puntero izquierdo del nodo en el árbol debe apuntar al predecesor y el puntero derecho del nodo en el árbol debe apuntar al sucesor. También debe devolver un puntero al elemento más pequeño de la lista vinculada.

Ejemplo 1:

Entrada: raíz = [4,2,5,1,3] 


Salida: [1,2,3,4,5]

Explicación: La siguiente figura muestra el árbol de búsqueda binaria transformado. La línea continua representa la relación sucesora y la línea de puntos representa la relación predecesora.

Ejemplo 2:

Entrada: raíz = [2,1,3]
Salida: [1,2,3]
Ejemplo 3:

Entrada: raíz = []
Salida: []
Explicación: La entrada es un árbol vacío, por lo que la salida también es una lista enlazada vacía.
Ejemplo 4:

Entrada: raíz = [1]
Salida: [1]
 

pista:

-1000 <= Node.val <= 1000
Node.left.val < Node.val < Node.right.val
Todos los valores de Node.val son únicos
0 <= Número de nodos <= 2000

//创建单节点双向循环链表
Node* getList(int val = 0)
{
	Node* p = new Node;
	p->left = p, p->right = p, p->val = val;
	return p;
}
//合并双向循环链表
void merge2list(Node* p1, Node* p2)
{
	if (!p1 || !p2)return;
	p1->left->right = p2, p2->left->right = p1;
	Node* tmp = p2->left;
	p2->left = p1->left;
	p1->left = tmp;
}

class Solution {
public:
	Node* treeToDoublyList(Node* root) {
		if (!root)return NULL;
		Node* n1 = getList(root->val);
		if (root->left) {
			Node* n2 = treeToDoublyList(root->left);
			merge2list(n2, n1);
			n1 = n2;
		}
		if (root->right) {
			Node* n3 = treeToDoublyList(root->right);
			merge2list(n1, n3);
		}
		return n1;
	}
};

Lista enlazada circular bidireccional cruzada (enlaces danzantes)

Los enlaces danzantes son una extensión de las listas enlazadas circulares bidireccionales y se utilizan para resolver de manera eficiente problemas de cobertura precisos.

Problema de cobertura precisa

Dada una matriz 0-1 con m filas yn columnas, seleccione una cantidad de filas para que no haya 1 en la misma columna entre cada 2 filas seleccionadas, y el número total de todos los 1 seleccionados sea n.

En otras palabras, dados m números, seleccione un número de modo que la operación de suma de cada dos de los números seleccionados sea 0, y el resultado de la operación O de todos los números seleccionados sea 2^n-1

El algoritmo X convierte la matriz 0-1 en enlaces danzantes y realiza una búsqueda DFS basada en esto.

Construya una tabla con m+1 filas y n+1 columnas (es decir, expanda la matriz 0-1 hacia arriba y hacia la izquierda una fila y una columna), represente todos los unos como nodos ordinarios, ignore 0 y agregue n+ 1 especial en la fila 0 Los nodos están numerados del 0 al n, donde el número 0 es el encabezado total y el número del 1 al n son los nodos del encabezado de las columnas del 1 al n. Es decir, los enlaces danzantes vacíos tienen n+1 nodos .

A partir de la tabla vacía, los nodos se agregan secuencialmente y se mantienen dinámicamente una lista circular bidireccional de cada columna y una lista circular bidireccional de cada fila. El orden es arbitrario (no es tan fluido como parece en la imagen, es decir , arriba, izquierda, abajo, etc. Es posible que dar un paso hacia la derecha no regrese al punto de partida, esto se implementa por eficiencia).

Cada nodo registra los números de sus cuatro nodos arriba, abajo, izquierda y derecha. Los números de nodo en la línea 0 son 0 an, y los números de nodos ordinarios son n + 1, n + 2 ...

Algoritmo X (versión recursiva V1)

Comience desde cualquier columna, vea qué filas en esta columna tienen 1, enumere estas filas para seleccionar qué fila, realice la operación del en todos los 1 en la fila seleccionada, continúe buscando y retroceda si no hay una solución.

Implementación de la versión recursiva:

class DancingLink
{
public:
	stack<int>rows;
	DancingLink(int m, int n, int maxNum)
	{
		this->m = m, this->n = n;
		rhead.resize(m + 1);
		row.resize(maxNum), col.resize(maxNum);
		up.resize(maxNum), down.resize(maxNum), lef.resize(maxNum), rig.resize(maxNum);
		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;
		}
		lef[0] = n, rig[n] = 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;
		}
	}
	bool dfs()
	{
		if (rig[0] == 0)return true;
		int c = rig[0];
		del(c);
		for (int i = down[c]; i != c; i = down[i])
		{
			for (int j = rig[i]; j != i; j = rig[j])del(col[j]);
			rows.push(row[i]);
			if (dfs())return true;
			rows.pop();
			for (int j = rig[i]; j != i; j = rig[j])reback(col[j]);
		}
		reback(c);
		return false;
	}
private:
	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];
	}
	void reback(int c)//完全回退del操作
	{
		lef[rig[c]] = rig[lef[c]] = c;
		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;
	}
private:
	int m, n, key;
	vector<int>row, col;//每个节点的行,列
	vector<int>rhead;//每行第一个节点的id
	vector<int>up, down, lef, rig;//每个节点上下左右的节点id
};

m y maxNum solo pueden ser grandes y no pequeños. Ser más grande no afecta el rendimiento.

Y n debe ser exactamente correcto.

POJ 3740 Búsqueda fácil

tema:

Dada una   matriz  A de  M ×  N. A  ij ∈ {0, 1} (0 ≤ i < M, 0 ≤ j < N), ¿podrías encontrar algunas filas que permitan que cada columna contenga y solo contenga un 1?

Aporte

Hay varios casos finalizados por  EOF . Caso de prueba hasta 500. La primera línea de entrada es  MN  (  M  ≤ 16,  N  ≤ 300). Las siguientes  M  líneas, cada línea contiene  N  números enteros separados por espacios.

Producción

Para cada caso de prueba, si puede encontrarlo, genere "Sí, lo encontré", de lo contrario, genere "Es imposible" por línea.

Entrada de muestra

3 3
0 1 0
0 0 1
1 0 0
4 4
0 0 0 1
1 0 0 0
1 1 0 1
0 1 0 0

Salida de muestra

Yes, I found it
It is impossible

Simplemente es una cuestión de cobertura precisa.

Código:

#include<iostream>
#include<vector>
#include<stdio.h>
using namespace std;

class DancingLink;

int main()
{
	int m, n, x;
	while (scanf("%d%d", &m, &n) != EOF)
	{
		DancingLink s(m, n, 10000);
		for (int i = 1; i <= m; i++)for (int j = 1; j <= n; j++)
		{
			scanf("%d", &x);
			if (x)s.push(i, j);
		}
		if (s.dfs())printf("Yes, I found it\n");
		else printf("It is impossible\n");
	}
	return 0;
}

Sudokus

Convierte el Sudoku en un problema de cobertura exacta.

Las 81 cuadrículas se atraviesan por turno, y cada cuadrícula tiene una determinada o 9 situaciones.

Para una cuadrícula con 9 situaciones, agregue 9 filas y para una cuadrícula determinada, solo es necesario agregar 1 fila.

Hay un total de 81*4 columnas, cada columna representa un cheque y hay cuatro tipos de cheque en total, que son:

Solo se puede completar un número en cada cuadrícula (81 cuadrículas). Cada número solo puede aparecer una vez en cada fila (9*9=81). Cada número solo puede aparecer una vez en cada columna (9*9=81). Cada número en la cuadrícula de nueve cuadrados solo puede aparecer una vez (9*9=81)

string Sudoku(string s, char cEmpty = '.')
{
	int num = 0;
	for (auto c : s)if (c != 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 "";
	stack<int>rows = d.rows;
	string ans = s;
	while (!rows.empty()) {
		int row = rows.top();
		rows.pop();
		int id = mrow[row];
		if (s[id] == cEmpty) {
			ans[id] = '1';
			while (mrow[row - 1] == id)ans[id]++, row--;
		}
		else ans[id] = s[id];
	}
	return ans;
}

Caso de prueba:

#include<cstdlib>
#include<ctime>
using namespace std;

int main()
{
	string s = ".2738..1..1...6735.......293.5692.8...........6.1745.364.......9518...7..8..6534.";
	clock_t start, endd;
	start = clock();
	cout << Sudoku(s);
	endd = clock();
	double endtime = (double)(endd - start) / CLOCKS_PER_SEC;
	cout << "Total time:" << endtime << endl; //s为单位
	return 0;
}

Producción:

527389416819426735436751829375692184194538267268174593643217958951843672782965341

V1 tarda 242 milisegundos y la búsqueda de fuerza bruta en POJ 3074 Sudoku tarda 8 milisegundos.

Optimización del algoritmo X

Algoritmo X (versión no recursiva V2)

Después de mucho esfuerzo, lo cambié a una versión no recursiva:

class DancingLink
{
public:
	stack<int>sc,rows;//覆盖选中的行,值的范围是从1到m
	DancingLink(int m, int n, int maxNum)
	{
		this->m = m, this->n = n;
		rhead.resize(m + 1);
		row.resize(maxNum), col.resize(maxNum);
		up.resize(maxNum), down.resize(maxNum), lef.resize(maxNum), rig.resize(maxNum);
		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;
		}
		lef[0] = n, rig[n] = 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;
		}
	}
	bool dfs()
	{
		while (true) {
			if (rig[0] == 0)return true;
			int c = rig[0];
			del(c);
			if (c == down[c]) {
				reback(c);
				if (sc.empty())return false;
				c = sc.top();
				sc.pop();
				rows.pop();
				for (int j = rig[c]; j != c; j = rig[j])reback(col[j]);
			}
			while (true) {
				c = down[c];
				if (c <= n) {
					reback(col[c]);
					if (sc.empty())return false;
					c = sc.top();
					sc.pop();
					rows.pop();
					for (int j = rig[c]; j != c; j = rig[j])reback(col[j]);
				}
				else break;
			}
			sc.push(c);//记录选中id
			rows.push(row[c]);
			for (int j = rig[c]; j != c; j = rig[j]) {
				del(col[j]);
			}
		}
		return false;
	}
private:
	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];
	}
	void reback(int c)//完全回退del操作
	{
		lef[rig[c]] = rig[lef[c]] = c;
		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;
	}
private:
	int m, n, key;
	vector<int>row, col;//每个节点的行,列
	vector<int>rhead;//每行第一个节点的id
	vector<int>up, down, lef, rig;//每个节点上下左右的节点id
};

Esta función dfs debería poder simplificarse, pero no se apresure a simplificarla todavía. Sólo asegúrese de que la lógica sea fácil de entender. Asegúrese de que la función sea correcta antes de reconstruirla.

V2 tardó 247 milisegundos

Algoritmo X (versión no recursiva V3)

Modifique directamente según la versión recursiva V1, reemplace la llamada recursiva y el retroceso con goto respectivamente.

class DancingLink
{
public:
	stack<int>sc, si, rows;
	DancingLink(int m, int n, int maxNum)
	{
		this->m = m, this->n = n;
		rhead.resize(m + 1);
		row.resize(maxNum), col.resize(maxNum);
		up.resize(maxNum), down.resize(maxNum), lef.resize(maxNum), rig.resize(maxNum);
		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;
		}
		lef[0] = n, rig[n] = 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;
		}
	}
	int c, i;
	bool dfs()
	{
	h1:
		c = rig[0];
		if (c == 0)return true;
		del(c);
		for (i = down[c]; i != c; i = down[i])
		{
			for (int j = rig[i]; j != i; j = rig[j])del(col[j]);
			sc.push(c), si.push(i), rows.push(row[i]);
			goto h1;
		h2:
			c = sc.top(), i = si.top(), sc.pop(), si.pop(),rows.pop();
			for (int j = rig[i]; j != i; j = rig[j])reback(col[j]);
		}
		reback(c);
		if (c != 1)goto h2;
		return false;
	}
private:
	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];
	}
	void reback(int c)//完全回退del操作
	{
		lef[rig[c]] = rig[lef[c]] = c;
		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;
	}
private:
	int m, n, key;
	vector<int>row, col;//每个节点的行,列
	vector<int>rhead;//每行第一个节点的id
	vector<int>up, down, lef, rig;//每个节点上下左右的节点id
};

V3 tardó 245 milisegundos

Algoritmo X (versión recursiva V4)

Cambie V1 a la forma antisimétrica de retroalimentación y del.

class DancingLink
{
public:
	DancingLink(int m, int n, int maxNum)
	{
		this->m = m, this->n = n;
		rhead.resize(m + 1);
		row.resize(maxNum), col.resize(maxNum);
		up.resize(maxNum), down.resize(maxNum), lef.resize(maxNum), rig.resize(maxNum);
		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;
		}
		lef[0] = n, rig[n] = 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;
		}
	}
	stack<int>rows;//覆盖选中的行,值的范围是从1到m
	bool dfs()
	{
		if (rig[0] == 0)return true;
		int c = rig[0];
		del(c);
		for (int i = down[c]; i != c; i = down[i])
		{
			for (int j = rig[i]; j != i; j = rig[j])del(col[j]);
			rows.push(row[i]);
			if (dfs())return true;
			rows.pop();
			for (int j = lef[i]; j != i; j = lef[j])reback(col[j]);
		}
		reback(c);
		return false;
	}
private:
	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];
	}
	void reback(int c)//完全回退del操作
	{
		lef[rig[c]] = rig[lef[c]] = c;
		for (int i = up[c]; i != c; i = up[i])
			for (int j = lef[i]; j != i; j = lef[j])
				down[up[j]] = up[down[j]] = j;
	}
private:
	int m, n, key;
	vector<int>row, col;//每个节点的行,列
	vector<int>rhead;//每行第一个节点的id
	vector<int>up, down, lef, rig;//每个节点上下左右的节点id
};

V4 tardó 219 milisegundos

Algoritmo X (versión no recursiva V5)

V2 se ha simplificado y la pila de salida se ha cambiado a un vector de salida. El tamaño del vector se ha establecido de antemano para reducir las operaciones de inserción y extracción de la pila.

class DancingLink
{
public:
	vector<int>sc, rows;//覆盖选中的行,值的范围是从1到m
	int scid = 0, rowsid = 0;
	DancingLink(int m, int n, int maxNum)
	{
		this->m = m, this->n = n, maxNum += n + 1;
		rhead.resize(m + 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;
		}
		lef[0] = n, rig[n] = 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;
		}
	}
	bool dfs()
	{
		while (true) {
			int c = rig[0];
			if (c == 0) {
				rows.resize(rowsid);
				return true;
			}
			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];
	}
	inline void reback(int c)//完全回退del操作
	{
		lef[rig[c]] = rig[lef[c]] = c;
		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;
	}
private:
	int m, n, key;
	vector<int>row, col;//每个节点的行,列
	vector<int>rhead;//每行第一个节点的id
	vector<int>up, down, lef, rig;//每个节点上下左右的节点id
};

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);
	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 (auto row:d.rows) {
		int id = mrow[row];
		if (s[id] == cEmpty) {
			ans[id] = '1';
			while (mrow[row - 1] == id)ans[id]++, row--;
		}
		else ans[id] = s[id];
	}
	return ans;
}

V5 tardó 223 milisegundos

Aceleración del algoritmo X (versión no recursiva V6)

La forma más sencilla y eficaz de acelerar la búsqueda es empezar a buscar con los menos probables.

Contamos el número de nodos en cada columna en tiempo real, y las columnas eliminadas y la columna 0 se consideran infinito positivo.

Cada vez, la búsqueda no comienza desde la plataforma [0], sino desde la columna con la menor cantidad de nodos.

class DancingLink
{
public:
	vector<int>rows;//覆盖选中的行,值的范围是从1到m
	DancingLink(int m, int n, int maxNum)
	{
		this->m = m, this->n = n, maxNum += n + 1;
		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;
		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 = 0, rowsid = 0;
};

Entre ellos, nums es registrar el número de elementos en cada columna y usar min_element para encontrar directamente la posición del valor mínimo.

V6 tarda 1 milisegundo

Algoritmo X (versión no recursiva V7 basada en recursividad de cola)

Después de leer este artículo, el colega sentado detrás de mí cambió la versión recursiva V1 a una versión recursiva de cola:

class DancingLink
{
public:
	std::stack<int>rows;
	DancingLink(int m, int n, int maxNum)
	{
		this->m = m, this->n = n;
		rhead.resize(m + 1);
		row.resize(maxNum), col.resize(maxNum);
		up.resize(maxNum), down.resize(maxNum), lef.resize(maxNum), rig.resize(maxNum);
		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;
		}
		lef[0] = n, rig[n] = 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;
		}
	}
	bool dfs()
	{
		if (rig[0] == 0) return true;
		if (opts.empty()) return false;
		auto runner = std::move(opts.top());
		opts.pop();
		runner();
		return dfs();
	}

	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];
	}
	void reback(int c)//完全回退del操作
	{
		lef[rig[c]] = rig[lef[c]] = c;
		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;
	}
	auto MakeOpt()->std::function<void()>
	{
		return [=] {
			int c = rig[0];
			del(c);
			auto cre = [=] {
				reback(c);
			};
			opts.push(std::move(cre));
			for (int i = up[c]; i != c; i = up[i]) {
				auto subpush = [=] {
					for (int j = rig[i]; j != i; j = rig[j])del(col[j]);
					rows.push(row[i]);
				};
				auto subre = [=] {
					rows.pop();
					for (int j = rig[i]; j != i; j = rig[j]) reback(col[j]);
				};
				opts.push(std::move(subre));
				opts.push(MakeOpt());
				opts.push(std::move(subpush));
			}
		};
	}
	auto Dfs() {
		opts.push(MakeOpt());
		return dfs();
	}
private:
	int m, n, key;
	std::vector<int>row, col;//每个节点的行,列
	std::vector<int>rhead;//每行第一个节点的id
	std::vector<int>up, down, lef, rig;//每个节点上下左右的节点id
	std::stack<std::function<void()>> opts;
};

Esta operación informará un desbordamiento de la pila porque la profundidad de recursividad es demasiado profunda.

Simplemente cámbielo a una versión no recursiva:

class DancingLink
{
public:
	std::stack<int>rows;
	DancingLink(int m, int n, int maxNum)
	{
		this->m = m, this->n = n;
		rhead.resize(m + 1);
		row.resize(maxNum), col.resize(maxNum);
		up.resize(maxNum), down.resize(maxNum), lef.resize(maxNum), rig.resize(maxNum);
		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;
		}
		lef[0] = n, rig[n] = 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;
		}
	}
	bool dfs()
	{
		while (true) {
			if (rig[0] == 0) return true;
			if (opts.empty()) return false;
			auto runner = std::move(opts.top());
			opts.pop();
			runner();
		}
	}

	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];
	}
	void reback(int c)//完全回退del操作
	{
		lef[rig[c]] = rig[lef[c]] = c;
		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;
	}
	auto MakeOpt()->std::function<void()>
	{
		return [=] {
			int c = rig[0];
			del(c);
			auto cre = [=] {
				reback(c);
			};
			opts.push(std::move(cre));
			for (int i = up[c]; i != c; i = up[i]) {
				auto subpush = [=] {
					for (int j = rig[i]; j != i; j = rig[j])del(col[j]);
					rows.push(row[i]);
				};
				auto subre = [=] {
					rows.pop();
					for (int j = rig[i]; j != i; j = rig[j]) reback(col[j]);
				};
				opts.push(std::move(subre));
				opts.push(MakeOpt());
				opts.push(std::move(subpush));
			}
		};
	}
	auto Dfs() {
		opts.push(MakeOpt());
		return dfs();
	}
private:
	int m, n, key;
	std::vector<int>row, col;//每个节点的行,列
	std::vector<int>rhead;//每行第一个节点的id
	std::vector<int>up, down, lef, rig;//每个节点上下左右的节点id
	std::stack<std::function<void()>> opts;
};

V7 tardó 398 milisegundos

Algoritmo X (versión final V8)

Entre las versiones anteriores, la V6 tiene el rendimiento más alto. He realizado ajustes basados ​​en esta versión para que pueda encontrar cualquier solución o todas las soluciones.

class DancingLink // 精确覆盖算法
{
public:
	DancingLink(int m, int n, int maxNum) //01矩阵的行、列、1的最大数量
	{
		this->m = m, this->n = n, maxNum += n + 1;
		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;
		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]++;
	}
	vector<vector<int>> getAllAns()
	{
		return dfs(false);
	}
	vector<int> getAnyAns()
	{
		auto v = dfs(true);
		if (v.size())return v[0];
		return vector<int>{};
	}
private:
	vector<vector<int>> dfs(bool onlyOne)
	{
		vector<vector<int>>ans;
		while (true) {
			if (rig[0] == 0) {
				rows.resize(rowsid);
				ans.push_back(rows);
				rows.resize(m);
				if (onlyOne)return ans;
			}
			int c = min_element(nums.begin() + 1, nums.end()) - nums.begin();
			if (rig[0] == 0)c = 0;
			del(c);
			while (true) {
				c = down[c];
				if (c > n)break;
				reback(col[c]);
				if (scid == 0)return ans;
				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 ans;
	}
	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 = 0, rowsid = 0;
	vector<int>rows;//覆盖选中的行,值的范围是从1到m
};

Aplicación del algoritmo X.

Problema de cobertura de empalme

El problema de cobertura de empalme también puede transformarse en un problema de cobertura exacta.

Cobertura de empalme de bloques bidimensionales no repetitivos

Simplemente enumere todos los bloques, cada posibilidad de cada bloque es una fila de enlaces danzantes y cada cuadrícula del área a cubrir es una columna. Además, también es necesario limitar cada bloque para que solo se use una vez, lo que también corresponde a una columna.

PD: Si hay dos bloques repetidos, trátelos como dos bloques no repetidos y no habrá problema.

struct Grid
{
	int r, c;
	Grid(int rr, int cc) :r(rr), c(cc) {}
	bool operator<(const Grid& g) const
	{
		if (r == g.r)return c < g.c;
		return r < g.r;
	}
};
struct Block //一个块的所有形态
{
	vector<vector<Grid>>grids;
	Block(vector<vector<Grid>>g, int maxDr, int maxDc, const map<Grid, int>& m)//块的所有形态在最小位置包含的格子,最大偏移,待覆盖区域包含的格子
	{
		this->m = m;
		for (auto& g2 : g) {
			for (int i = 0; i < maxDr; i++)for (int j = 0; j < maxDc; j++) {
				for (auto& x : g2)x.r += i, x.c += j;
				if (inBoard(g2))grids.push_back(g2);
				for (auto& x : g2)x.r -= i, x.c -= j;
			}
		}
	}
	Block() {}
private:
	map<Grid, int>m;
	bool inBoard(vector<Grid>& g)
	{
		for (auto& x : g)if (!m[x])return false;
		return true;
	}
};

vector<vector<Grid>> Cover(vector<Block>blocks, map<Grid, int>& mg)//所有块,待覆盖区域包含的格子编号为1,2,3...
{
	int m = 0, maxNum = 0;
	for (auto& block : blocks) {
		m += block.grids.size();
		maxNum += block.grids.size() * (block.grids[0].size()+1);
	}
	DancingLink d(m, mg.size() + blocks.size(), maxNum);
	int r = 0;
	map<int, int>mrow;
	for (int i = 0; i < blocks.size(); i++) {
		mrow[r + 1] = i + 1;
		for (auto& grids : blocks[i].grids) {
			++r;
			for (auto& g : grids)d.push(r, mg[g]);
			d.push(r, i + 1 + mg.size());
		}
	}
	d.dfs();
	vector<int> rows = d.rows;
	vector<vector<Grid>> ans;
	for (auto row : rows) {
		int id = 0;
		while (!mrow[row])row--, id++;;
		ans.push_back(blocks[mrow[row] - 1].grids[id]);
	}
	return ans;
}

Ejemplos de aplicación:

rompecabezas de calendario

Superposición de empalme de bloques repetidos 3D

Si no hay límite en el número de algunos bloques (desde 0 hasta infinito positivo), entonces solo necesita cancelar la columna correspondiente a la restricción "Este bloque solo se puede usar una vez", y los demás permanecen sin cambios.

struct Grid
{
	int r, c, h;
	Grid(int rr, int cc,int hh) :r(rr), c(cc),h(hh) {}
	bool operator<(const Grid& g) const
	{
		if (h == g.h) {
			if (r == g.r)return c < g.c;
			return r < g.r;
		}
		return h < g.h;
	}
};
struct Block //一个块的所有形态
{
	vector<vector<Grid>>grids;
	Block(vector<vector<Grid>>g, int maxDr, int maxDc, int maxDh, const map<Grid, int>& m)//块的所有形态在最小位置包含的格子,最大偏移,待覆盖区域包含的格子
	{
		this->m = m;
		for (auto& g2 : g) {
			for (int i = 0; i < maxDr; i++)for (int j = 0; j < maxDc; j++)for(int k=0;k<maxDh;k++) {
				for (auto& x : g2)x.r += i, x.c += j, x.h += k;
				if (inBoard(g2))grids.push_back(g2);
				for (auto& x : g2)x.r -= i, x.c -= j, x.h -= k;;
			}
		}
	}
	Block() {}
private:
	map<Grid, int>m;
	bool inBoard(vector<Grid>& g)
	{
		for (auto& x : g)if (!m[x])return false;
		return true;
	}
};

vector<vector<Grid>> Cover(vector<Block>blocks, map<int,int>ids, map<Grid, int>& mg)//所有块,不限数量的块的id, 待覆盖区域包含的格子编号为1,2,3...
{
	int m = 0, maxNum = 0;
	for (auto& block : blocks) {
		m += block.grids.size();
		maxNum += block.grids.size() * (block.grids[0].size() + 1);
	}
	DancingLink d(m, mg.size() + blocks.size() - ids.size(), maxNum);
	int r = 0, c = mg.size();
	map<int, int>mrow;
	for (int i = 0; i < blocks.size(); i++) {
		mrow[r + 1] = i + 1;
		for (auto& grids : blocks[i].grids) {
			++r;
			for (auto& g : grids)d.push(r, mg[g]);
			if(ids[i]==0)d.push(r, ++c);
		}
	}
	d.dfs();
	vector<int> rows = d.rows;
	vector<vector<Grid>> ans;
	for (auto row : rows) {
		int id = 0;
		while (!mrow[row])row--, id++;;
		ans.push_back(blocks[mrow[row] - 1].grids[id]);
	}
	return ans;
}

Ejemplos de aplicación:

Rompecabezas 3D en forma de T

int r,c,h,blockNum; //自定义行列数,块数
map<Grid, int> ng,mg;  //ng是自定义挖掉的格子,mg是有效格子
vector<Block>blocks;//自定义每个块的所有形态在最小位置包含的格子
map<int, int>ids;

vector<Grid> rotate(vector<Grid>& g)
{
	int maxRow = 0, t;
	for (auto& gi : g)maxRow = max(maxRow, gi.r);
	for (auto& gi : g)t = gi.c, gi.c = maxRow - gi.r, gi.r = t;
	return g;
}
vector<Grid> rotate2(vector<Grid> g)
{
	int maxH = 0, t;
	for (auto& gi : g)maxH = max(maxH, gi.h);
	for (auto& gi : g)t = gi.c, gi.c = maxH - gi.h, gi.h = t;
	return g;
}

void init1()
{
	r = 6, c = 6, h=6, blockNum = 1;
	ng.clear(), mg.clear();
}
void init2()
{
	vector<Grid>v1 = { {0,0,0},{0,1,0},{0,2,0},{1,1,0} };
	vector<Grid>v2 = { {0,0,0},{0,1,0},{0,2,0},{0,1,1} }, v3 = rotate2(v2), v4 = rotate2(v3), v5 = rotate2(v4);
	blocks[0] = Block{ { v1,rotate(v1),rotate(v1),rotate(v1),v2,rotate(v2),v3,rotate(v3),v4,rotate(v4),v5,rotate(v5)}, r, c,h, mg };
	ids[0] = 1;
}

void solve()
{
	init1();
	int id = 0;
	for (int i = 0; i < r; i++)for (int j = 0; j < c; j++) for (int k = 0; k < h; k++) {
		if (ng[Grid{ i, j ,k }] == 0)mg[Grid{ i, j,k }] = ++id;
	}
	blocks.resize(blockNum);
	init2();
	vector<vector<Grid>> grids = Cover(blocks,ids, mg);
	vector<vector<vector<int>>>v(r);
	for (int i = 0; i < r; i++) {
		v[i].resize(c);
		for (int j = 0; j < c; j++)v[i][j].resize(h);
	}
	for (int i = 0; i < grids.size(); i++) {
		for (auto& g : grids[i])v[g.r][g.c][g.h] = i + 1;
	}
	for (int i = 0; i < r; i++) {
		for (int j = 0; j < c; j++) {
			for (int k = 0; k < h; k++)cout << v[i][j][k] << " ";
			cout << endl;
		}
		cout << endl;
	}
}


int main()
{
	ios::sync_with_stdio(false);
	clock_t start, endd;
	start = clock();
	freopen("D:ans.txt", "w", stdout);
	solve();
	endd = clock();
	double endtime = (double)(endd - start) / CLOCKS_PER_SEC;
	cout << "Total time:" << endtime << endl; //s为单位
	return 0;
}

Producción:

1 1 1 2 2 2 7 
7 7 10 10 10 
20 20 20 23 23 23 21 
21 21 31 31 31 
24 21 37 36 31 32 3 
3 3 4 4 4 

5 1 8 9 2 11 13 
7 9 9 10 27 
25 20 35 9 23 27 
28 35 35 35 49 27 
24 37 37 36 32 32 24 
3 39 36 4 33 

5 5 8 8 11 11 13 13 
34 42 42 42 
25 25 34 34 42 27 28 28 34 49 
49 49 24 
30 37 36 48 32 26 
39 39 41 33 33 

5 14 8 43 44 11 
13 29 43 43 43 54 
25 29 29 50 54 54 
28 29 50 50 50 54 
30 30 40 48 48 48 26 
26 39 41 41 33 

14 14 14 44 44 44 
16 16 16 51 51 51 
18 16 17 52 51 53 
18 18 40 52 52 47 
18 30 40 52 47 47 
26 19 40 41 38 47 

6 6 6 12 12 12 
15 6 17 45 12 53 
15 15 17 45 45 53 
15 22 17 45 46 53 
22 22 22 46 46 46 
19 19 19 38 38 38 

Tiempo total: 0,953
 

problema de coincidencia

Dado un gráfico no dirigido con 2n puntos, seleccione n aristas que cubran estos 2n puntos.

Utilicé el código más reciente para implementar el código y encontrar todas las soluciones:

	//给定一个2n个点的图,选出n条边,刚好覆盖这2n个点
	static vector<vector<Edge>> getEdgeCover(int n, vector<Edge>& v)
	{
		DancingLink d(v.size(), n * 2, v.size() * 2);
		for (int i = 0; i < v.size(); i++) {
			d.push(i, v[i].a + 1);
			d.push(i, v[i].b + 1);
		}
		vector<vector<Edge>>ans;
		vector<vector<int>> vrow = d.getAllAns();
		for (auto vi : vrow)ans.push_back(fgetNumFromId(v, vi));
		return ans;
	}

belleza de la simetría

La belleza de la simetría se resuelve automáticamente

Algoritmo de búsqueda de números agrupados

Juntamos el Sudoku estándar y el Sudoku irregular y los abstraemos en problemas de búsqueda de números basados ​​en grupos.

Parte del código reutiliza el código anterior en este artículo y el código de la plantilla del algoritmo de búsqueda .


class SearchWithGroupSum //分组数字搜索算法
{
public:
	SearchWithGroupSum(vector<vector<int>>& gridGroup, int row, int col, int low, int high) :gridGroup{ gridGroup },row { row }, col{ col },low{low},high{high}
	{
		anti.resize(row*col);
		grid.resize(row*col);
		for (int g = 0; g < gridGroup.size(); g++) {
			for (int i = 0; i < gridGroup[g].size(); i++)anti[gridGroup[g][i]].push_back(g);
			maxNum += gridGroup[g].size();
		}
	}
	void setGrid(vector<int>& grid)//除了已知数字之外都填0,有type=1的格子时需要调用本接口
	{
		this->grid = grid;
	}
	void setType(vector<int>& type)//有type=1或-1的格子时需要调用本接口
	{
		this->type = type;
	}
	vector<int> getAnyAns()
	{
		int m = 0;
		for (auto k : type) {
			if (k == 0)m += high - low + 1;
			else if (k == 1)m += 1;
		}
		int n = gridGroup.size()*(high - low + 1) + row * col;
		DancingLink d(m, n, (maxNum+row*col)*(high-low+1));
		int r = 0;
		map<int, int>mrow;
		mrow[0] = -1;
		for (int i = 0; i < row*col; i++) {
			if (type[i] == -1)continue;
			int lw = low, hh = high;
			if (type[i] == 1)lw = hh = grid[i];
			for (int x = lw; x <= hh; x++) {
				d.push(++r, i + 1);
				for (auto k : anti[i]) {
					d.push(r, k*(high - low + 1) + x - low + row * col + 1);
				}
				mrow[r] = i;
			}
		}
		
		vector<int> ans = d.getAnyAns();
		for (auto rowId : ans) {
			int id = mrow[rowId];
			grid[id] += type[id]?0:low;
			while (id == mrow[rowId - 1])rowId--, grid[id]++;
		}
		return grid;
	}
private:
	int row, col;//网格行列数
	int low, high;//每个格子填入数字的范围是[low,high],  限制一:low>0
	vector<int>type;//标识所有格子类型,0是需要填数字,1是已知数字,-1是无效格子
	vector<int>grid;//所有格子中的数字
	vector<vector<int>>gridGroup;//每一组有哪些格子
	vector<vector<int>>anti;//每个格子属于哪些组
	int maxNum = 1;
};

Sudoku estándar


string Sudoku(string s, char cEmpty = '.')
{
	vector<vector<int>>gridGroup;
	vector<int>v;
	for (int i = 0; i < 9; i++) {
		v.clear();
		for (int j = 0; j < 9; j++)v.push_back(i * 9 + j);
		gridGroup.push_back(v);
		v.clear();
		for (int j = 0; j < 9; j++)v.push_back(j * 9 + i);
		gridGroup.push_back(v);
	}
	for (int i = 0; i < 3; i++)for (int j = 0; j < 3; j++) {
		v.clear();
		for (int r = i * 3; r < i * 3 + 3; r++)for (int c = j * 3; c < j * 3 + 3; c++)v.push_back(r * 9 + c);
		gridGroup.push_back(v);
	}
	SearchWithGroupSum opt(gridGroup, 9, 9, 1, 9);
	vector<int>grid(81);
	vector<int>type(81);
	for (int i = 0; i < 81; i++)if (s[i] != cEmpty)grid[i] = s[i] - '0', type[i] = 1;
	opt.setGrid(grid);
	opt.setType(type);
	v = opt.getAnyAns();
	string ans(81, '0');
	for (int i = 0; i < 81; i++)ans[i] = v[i] + '0';
	return ans;
}

int main()
{
	ios::sync_with_stdio(false);
	string s;
	while (cin >> s)
	{
		auto s1 = clock();
		if (s == "end")return 0;
		cout << Sudoku(s) << endl;
		auto e1 = clock();
		cout << endl << e1 - s1;
	}
	return 0;
}

ingresar

.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.

producción

527389416819426735436751829375692184194538267268174593643217958951843672782965341
416837529982465371735129468571298643293 746185864351297647913852359682714128574936

Cada uno toma 1 milisegundo

Sudokus irregulares


string Sudoku(string s, vector<int>&par, char cEmpty = '.', int parEmpty = -1)
{
	vector<vector<int>>gridGroup;
	vector<int>v;
	map<int, vector<int>>m;
	for (int i = 0; i < 9; i++) {
		v.clear();
		for (int j = 0; j < 9; j++)v.push_back(i * 9 + j);
		gridGroup.push_back(v);
		v.clear();
		for (int j = 0; j < 9; j++) {
			v.push_back(j * 9 + i);
			if (par[i * 9 + j] != parEmpty)m[par[i * 9 + j]].push_back(i * 9 + j);
		}
		gridGroup.push_back(v);
	}
	for (auto mi : m)gridGroup.push_back(mi.second);
	vector<int> groupSum(27, 45);
	SearchWithGroupSum opt(gridGroup, 9, 9, 1, 9);
	vector<int>grid(81);
	vector<int>type(81);
	for (int i = 0; i < 81; i++)if (s[i] != cEmpty)grid[i] = s[i] - '0', type[i] = 1;
	opt.setGrid(grid);
	opt.setType(type);
	v = opt.getAnyAns();
	string ans(81, '0');
	for (int i = 0; i < 81; i++)ans[i] = v[i] + '0';
	return ans;
}

int main()
{
	ios::sync_with_stdio(false);
	string s;
	vector<int>par(81);
	while (cin >> s)
	{
		for (int i = 0; i < 81; i++)cin >> par[i];
		auto s1 = clock();
		if (s == "end")return 0;
		cout << Sudoku(s, par) << endl;
		auto e1 = clock();
		cout << endl << e1 - s1;
	}
	return 0;
}

ingresar

.3.159.8.2.9...6.3..78.34..9...4...57.6...1.83...9...6..29.75..5.1...8.2.7.516.2 .
1 2 2 3 3 3 4 
4 4 1
2 2 3 3 3 4 4 4 1 2 2 2 3 3 4 4 4
1 2 5 2 5 3 6 6 6 1
1 5 5 5 5 5 6 6
1 1 1 8 5 9 5 9 6
7 7 7 8 8 9 9 9 6
7 7 7 8 8 8
9 9 6 7 7 7 8 8 8 9 9 6

producción

634159287259478613127863459918642375746325198385291746462987531591734862873516924

Tarda 0 milisegundos

Algoritmo de búsqueda de números de suma agrupada

A diferencia de la plantilla de algoritmo de búsqueda , aquí tratamos los dos conceptos de agrupación numérica mutuamente excluyente, determinación numérica y agrupación por separado.

Todos los grupos son grupos numéricamente exclusivos, pero sólo algunos grupos son grupos numéricamente exclusivos.

Limitamos el escenario de uso al hecho de que no hay cuadrículas repetidas entre la determinación de números y la agrupación, pero puede haber cuadrículas que no estén dentro de ninguna determinación y agrupación de números.


set<int> ToSet(int low, int high)
{
	set<int>ans;
	for (int i = low; i <= high; i++)ans.insert(i);
	return ans;
}

class SearchWithGroupSum //分组搜索算法
{
public:
	SearchWithGroupSum(vector<vector<int>>& gridGroup, vector<vector<int>>& gridGroup2,const map<int, vector<vector<int>>> &spRet, int row, int col, int low, int high)
		:gridGroup{ gridGroup }, gridGroup2{ gridGroup2 },spRet{ spRet }, row{ row }, col{ col }, low{ low }, high{ high }
	{
		anti.resize(row*col);
		grid.resize(row*col);
		for (int g = 0; g < gridGroup.size(); g++) {
			for (int i = 0; i < gridGroup[g].size(); i++)anti[gridGroup[g][i]].push_back(g);
		}
	}
	void setGrid(vector<int>& grid)//除了已知数字之外都填0,有type=1的格子时需要调用本接口
	{
		this->grid = grid;
	}
	void setType(vector<int>& type)//有type=1或-1的格子时需要调用本接口
	{
		this->type = type;
	}
	vector<int> getAnyAns()
	{
		int m = (high - low + 1) * row * col + NumInVec2D(GetSecond(spRet));
		int n = gridGroup.size() * (high - low + 1) + row * col;
		DancingLink d(m, n, (row * col + NumInVec2D(GetSecond(spRet)))*(high - low + 1) * row);
		int r = 0;
		map<int, int>mrow, mrow2;
		mrow[0] = -1;
		map<int, int>visit;
		for (auto &mp : spRet) {
			auto &sp = mp.second;
			for (int i = 0; i < sp.size();i++) {
				auto &v = sp[i];
				mrow[++r] = i, mrow2[r] = mp.first;
				for (int i = 0; i < v.size(); i++) {
					d.push(r, gridGroup2[mp.first][i] + 1);
					for (auto k : anti[gridGroup2[mp.first][i]]) {
						d.push(r, k*(high - low + 1) + v[i] - low + row * col + 1);
					}
					visit[gridGroup2[mp.first][i]] = 1;
				}
			}
		}
		int kr = r;
		for (int i = 0; i < row*col; i++) {
			if (type[i] == -1 || visit[i] == 1)continue;
			int lw = low, hh = high;
			if (type[i] == 1)lw = hh = grid[i];
			for (int x = lw; x <= hh; x++) {
				mrow[++r] = i;
				d.push(r, i + 1);
				for (auto k : anti[i]) {
					d.push(r, k*(high - low + 1) + x - low + row * col + 1);
				}
			}
		}
		vector<int> ans = d.getAnyAns();
		for (auto rowId : ans) {
			if (rowId > kr) {
				int id = mrow[rowId];
				grid[id] += type[id] ? 0 : low;
				while (rowId > kr + 1 && id == mrow[rowId - 1])rowId--, grid[id]++;
			}
			else {
				int id = mrow[rowId];
				auto v = spRet[mrow2[rowId]][id];
				for (int i = 0; i < gridGroup2[mrow2[rowId]].size(); i++)grid[gridGroup2[mrow2[rowId]][i]] = v[i];
			}
		}
		return grid;
	}
private:
	int row, col;//网格行列数
	int low, high;//每个格子填入数字的范围是[low,high],  限制一:low>0
	vector<int>type;//标识所有格子类型,0是需要填数字,1是已知数字,-1是无效格子
	vector<int>grid;//所有格子中的数字
	vector<vector<int>>gridGroup;//互斥组,每一组有哪些格子
	vector<vector<int>>gridGroup2;//定和组,每一组有哪些格子
	vector<vector<int>>anti;//每个格子属于哪些组
	map<int, vector<vector<int>>> spRet;
};

Cobertura completa de Killer Sudoku


map<int, vector<vector<int>>> getSplitRet(vector<vector<int>>& gridGroup, map<int, int>&groupSum)
{
	return MapTrans<int, int, vector<vector<int>>>(groupSum, [&](pair<int, int>p) {
		return IntSplitA(groupSum[p.first], gridGroup[p.first].size(), ToSet(1, 9));
	});
}
string Sudoku(string s, vector<int>&par, vector<int>&groupSum, char cEmpty = '.', int parEmpty = -1)
{
	vector<vector<int>>gridGroup, gridGroup2;
	vector<int>v;
	map<int, vector<int>>m;
	for (int i = 0; i < 9; i++) {
		v.clear();
		for (int j = 0; j < 9; j++)v.push_back(i * 9 + j);
		gridGroup.push_back(v);
		v.clear();
		for (int j = 0; j < 9; j++) {
			v.push_back(j * 9 + i);
			if (par[i * 9 + j] != parEmpty)m[par[i * 9 + j]].push_back(i * 9 + j);
		}
		gridGroup.push_back(v);
	}
	for (int i = 0; i < 3; i++)for (int j = 0; j < 3; j++) {
		v.clear();
		for (int r = i * 3; r < i * 3 + 3; r++)for (int c = j * 3; c < j * 3 + 3; c++)v.push_back(r * 9 + c);
		gridGroup.push_back(v);
	}
	for (auto mi : m)gridGroup2.push_back(mi.second);
	map<int, int>mGroupSum;
	for (int i = 0; i < groupSum.size(); i++)mGroupSum[i] = groupSum[i];
	auto spRet = getSplitRet(gridGroup2, mGroupSum);
	SearchWithGroupSum opt(gridGroup, gridGroup2, spRet, 9, 9, 1, 9);
	vector<int>grid(81);
	vector<int>type(81);
	for (int i = 0; i < 81; i++)if (s[i] != cEmpty)grid[i] = s[i] - '0', type[i] = 1;
	opt.setGrid(grid);
	opt.setType(type);
	v = opt.getAnyAns();
	string ans(81, '0');
	for (int i = 0; i < 81; i++)ans[i] = v[i] + '0';
	return ans;
}

int main()
{
	ios::sync_with_stdio(false);
	string s;
	vector<int>groupSum;
	vector<int>par(81);
	while (cin >> s)
	{
		int x;
		while (cin >> x) {
			if (x == 0)break;
			groupSum.push_back(x);
		}
		for (int i = 0; i < 81; i++)cin >> par[i];
		auto s1 = clock();
		if (s == "end")return 0;
		cout << Sudoku(s, par, groupSum) << endl;
		auto e1 = clock();
		cout << endl << e1 - s1;
	}
	return 0;
}

ingresar:

................................................. .................................
15 24 11 17 5 14 10 16 10 15 10 9 10 21 7 20 15 15 12 17 8 16 18 10 13 12 13 32 10 0
 1 2 3 3 5 5 8 8 8
 1 2 2 4 6 7 7 9 9 1
 2 2 4 6 6 14 15 9 10 10 12 13 13 14
14 15 16
11 11 1 2 19 18 17 17 16 16 20 19 19 19 18 22
17 16 23 20 20 21 21 22 22 22 23 23 24 25 25 25 25 28 28 28 28 24 26 26
27
27
27 29 2 9 28

Salida:
946532781183974652527816943691287534378495216452163897765348129814629375239751468

Tarda 40 milisegundos

Sudoku asesino sin cobertura completa

El código es exactamente el mismo que el Sudoku Killer de cobertura total.

ingresar:

................................................. .................................
26 14 16 12 16 6 14 14 12 8 20 12 26 22 6 6 38 16 14 6 14 0
-1 1 2 2 3 3 -1 4 -1
1 1 1 5 5 3 -1 4 4
6 1 -1 5 5 8 8 -1 7 6
9 9 -1 10 10 11 12 7
13 13 13 14 -1 10 11 12 12
15 13 13 14 14 -1 11 11 21
15 -1 16 16 17 17 -1 -1 21 18 -1 -1
17 17 17 -1 19 19 18 18
-1 17 17 20 20 19 - 1

Producción:

956834217742169583183725946539241768867953124214678395421587639395416872678392451
tomó 274 milisegundos

Supongo que te gusta

Origin blog.csdn.net/nameofcsdn/article/details/132225150
Recomendado
Clasificación