双向循环链表、dancing links

news2025/6/19 8:06:10

目录

双向循环链表

力扣 426. 将二叉搜索树转化为排序的双向链表

十字交叉双向循环链表(dancing links)

精确覆盖问题

X算法(V1递归版)

POJ 3740 Easy Finding

数独

X算法优化

X算法(V2非递归版)

X算法(V3非递归版)

X算法(V4递归版)

X算法(V5非递归版)

X算法加速(V6非递归版)

X算法(V7基于尾递归的非递归版)

X算法(V8最终版)

X算法应用

拼接覆盖问题

二维非重复块拼接覆盖

三维重复块拼接覆盖

匹配问题

对称之美

分组数字搜索算法

标准数独

不规则数独

分组定和数字搜索算法

满覆盖杀手数独

非满覆盖杀手数独


双向循环链表

力扣 426. 将二叉搜索树转化为排序的双向链表

将一个 二叉搜索树 就地转化为一个 已排序的双向循环链表 。

对于双向循环列表,你可以将左右孩子指针作为双向循环链表的前驱和后继指针,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。

特别地,我们希望可以 就地 完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中最小元素的指针。

示例 1:

输入:root = [4,2,5,1,3] 


输出:[1,2,3,4,5]

解释:下图显示了转化后的二叉搜索树,实线表示后继关系,虚线表示前驱关系。

示例 2:

输入:root = [2,1,3]
输出:[1,2,3]
示例 3:

输入:root = []
输出:[]
解释:输入是空树,所以输出也是空链表。
示例 4:

输入:root = [1]
输出:[1]
 

提示:

-1000 <= Node.val <= 1000
Node.left.val < Node.val < Node.right.val
Node.val 的所有值都是独一无二的
0 <= Number of Nodes <= 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;
	}
};

十字交叉双向循环链表(dancing links)

dancing links是双向循环链表的拓展,用于高效解决精确覆盖问题。

精确覆盖问题

给定一个m行n列的0-1矩阵,选出若干行,使得选出的每2行之间没有在同一列的1,所有选出的1的总数为n。

换句话说就是给定m个数,选出若干个,使得选出的数中每2个的且运算都是0,所有选出的数的或运算结果是2^n-1

X算法就是把0-1矩阵转化成dancing links,基于此进行DFS搜索。

构建一个m+1行n+1列的表格(即把0-1矩阵往上往左拓展一行一列),把所有的1表示成普通节点,忽略0,在第0行添加n+1个特殊节点,编号分别是0到n,其中0号是总head,1到n号是1到n列的列头节点,即空的dancing links有n+1个节点。

从空表开始依次添加节点,动态维护每一列的双向循环列表和每一行的双向循环列表,其中的顺序是任意的(并不像图中看起来这么平齐,即往上往左往下往右各走一步可能不会回到起点,这样实现是为了高效)。

每个节点都记录了它上下左右四个节点的编号,第0行的节点编号是0到n,普通节点的编号是n+1,n+2......

X算法(V1递归版)

从任意一列开始,看这一列哪些行有1,枚举这些行选中哪一行,把选中行的所有1都执行del操作,继续往下搜索,如果没有解就回溯。

递归版实现:

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和maxNum只能大不能小,大一些不影响性能。

而n必须完全正确。

POJ 3740 Easy Finding

题目:

Given a M× N matrix AA ij ∈ {0, 1} (0 ≤ i < M, 0 ≤ j < N), could you find some rows that let every cloumn contains and only contains one 1.

Input

There are multiple cases ended by EOF. Test case up to 500.The first line of input is MN ( M ≤ 16, N ≤ 300). The next M lines every line contains N integers separated by space.

Output

For each test case, if you could find it output "Yes, I found it", otherwise output "It is impossible" per line.

Sample Input

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

Sample Output

Yes, I found it
It is impossible

单纯的精确覆盖问题。

代码:

#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;
}

数独

把数独转化成精确覆盖问题。

依次遍历81个格子,每个格子要么是确定的,要么是9种情况。

对于一个格子有9种情况的,添加9行,对于确定的格子只需要添加1行。

而列一共有81*4列,每一列代表一个校验,一共四种校验类型,分别是:

每个格子只能填1个数(81个格子),每行每个数只能出现一次(9*9=81),每列每个数只能出现一次(9*9=81),每个九宫格每个数只能出现一次(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;
}

测试用例:

#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;
}

输出:

527389416819426735436751829375692184194538267268174593643217958951843672782965341

V1耗时242毫秒,POJ 3074 Sudoku里面的暴力搜索耗时是8毫秒。

X算法优化

X算法(V2非递归版)

废了好大一股劲,改成了非递归版:

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
};

这个dfs函数应该还可以简化,但是先不急着简化,逻辑通俗易懂就行,先确认功能正确再重构。

V2耗时247毫秒

X算法(V3非递归版)

直接根据V1递归版进行修改,把递归调用和回溯分别换成goto

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耗时245毫秒

X算法(V4递归版)

把V1改成reback和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耗时219毫秒

X算法(V5非递归版)

把V2做了简化,同时把输出栈改成输出vector,提前把vector的尺寸设定好,减少入栈出栈操作。

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耗时223毫秒

X算法加速(V6非递归版)

搜索最简单有效的加速,就是先从可能性少的开始搜。

我们实时统计每一列的节点个数,删掉的列和第0列都认为是正无穷。

每次不是从rig[0]开始搜,而是从节点最少的那一列开始搜。

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;
};

其中nums就是记录每一列的元素个数,用min_element直接找到其中最小值的位置。

V6耗时1毫秒

X算法(V7基于尾递归的非递归版)

坐我后面的同事看了本文之后,把V1递归版变成了尾递归版:

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;
};

这个运行会报栈溢出,因为递归深度太深了。

直接把它改成非递归版:

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耗时398毫秒

X算法(V8最终版)

前面几个版本中,V6是性能最高的,我在这个版本的基础上做了微调,使得它既可以求任意一个解,也可以求所有解。

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
};

X算法应用

拼接覆盖问题

拼接覆盖问题也可以转成精确覆盖问题。

二维非重复块拼接覆盖

只要枚举所有的块,每个块的每一种可能性就是dancing links的一行,待覆盖区域的每个格子就是一列。除此之外,还要限制每个块只能被用1次,这也是对应一列。

PS:如果有2个重复的块,直接当他们是2个不重复的块,也没有问题。

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;
}

应用示例:

日历拼图

三维重复块拼接覆盖

假如部分块是没有数量限制的(从0到正无穷都可以),那么只需要取消“这个块只能被用1次”这个限制对应的列即可,其他的不变。

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;
}

应用示例:

三维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;
}

输出:

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 

Total time:0.953
 

匹配问题

给定一个2n个点的无向图,选出n条边,刚好覆盖这2n个点。

我用最新的代码,实现了求出所有解的代码:

	//给定一个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;
	}

对称之美

对称之美自动求解

分组数字搜索算法

我们把标准数独和不规则数独放在一起,抽象成基于分组的数字搜索问题。

一些代码复用了本文前面的代码和搜索算法模板里面的代码。


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;
};

标准数独


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;
}

输入

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

输出

527389416819426735436751829375692184194538267268174593643217958951843672782965341
416837529982465371735129468571298643293746185864351297647913852359682714128574936

各耗时1毫秒

不规则数独


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;
}

输入

.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

输出

634159287259478613127863459918642375746325198385291746462987531591734862873516924

耗时0毫秒

分组定和数字搜索算法

和搜索算法模板不同的是,这里我们把数字互斥分组、数字定和分组2个概念分开处理。

所有分组都是数字互斥分组,但只有部分分组是数字定和分组。

我们把使用场景限定为,数字定和分组之间没有重复的格子,但是可以有格子不在任一数字定和分组之内。


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;
};

满覆盖杀手数独


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;
}

输入:

.................................................................................
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 12 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 29 28

输出:
946532781183974652527816943691287534378495216452163897765348129814629375239751468

耗时40毫秒

非满覆盖杀手数独

代码和满覆盖杀手数独完全相同。

输入:

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

输出:

956834217742169583183725946539241768867953124214678395421587639395416872678392451
耗时274毫秒

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/862338.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用Python爬取某查查APP端(Appium自动化篇)

1. 写在前面 某查查网站反爬虫风控还是较强的&#xff0c;之后会分别介绍一下PC端协议、APP端自动化、APP端接口协议三种采集方案。这里主要介绍APP端的自动化方式&#xff0c;APP端自动化方式需要登陆账号&#xff0c;协议的话需要签名授权&#xff08;自动化经测试没有太多限…

生产事故-走近科学之消失的JWT

0x01 事故背景 2021年11月26日01时10分&#xff0c;P公司正在进行某业务系统的生产环境部署操作&#xff0c;但其实早在00时30分的时候&#xff0c;他们已经完成过一次部署了&#xff0c;但是奇怪的是无论如何都通不过验证&#xff0c;无奈只好推倒重来&#xff0c;如此反复了…

opencv实战项目 实现手势跟踪并返回位置信息(封装调用)

OpenCV 是一个基于 Apache2.0 许可&#xff08;开源&#xff09;发行的跨平台计算机视觉和机器学习软件库&#xff0c;可以运行在Linux、Windows、Android和Mac OS操作系统上。 需要提前准备opencv 和 mediapipe库 pip --default-timeout5000 install -i https://pypi.tuna.tsi…

腾讯云服务器使用教程_手把手图文详细介绍

腾讯云服务器使用教程包括注册账号实名认证、选择云服务器CVM或轻量应用服务器CPU内存带宽和系统盘配置、安全设置和云服务器远程连接、安全组端口开通教程、云服务器环境部署以搭建网站为例手把手网站上线&#xff0c;云服务器文件传输和数据备份以及技术支持等详细说明&#…

【Linux】用户和权限

文章目录 前言什么是 root 用户su 命令和 exit 命令sudo 命令为普通用户配置 sudo 认证 用户、用户组管理什么是Linux 用户和用户组用户组管理用户管理创建用户删除用户查看用户所属组将指定用户添加到指定用户组中 查看当前系统的用户和用户组 权限控制权限信息 修改权限控制修…

MyBatisX自动和数据库的字段关联

先下个插件 右键数据库&#xff0c;点MybatisX-Generator 在根据自己需求勾选 actual column&#xff1a;保证数据库的驼峰命名生效

第10集丨Vue 江湖 —— 表单输入绑定

目录 一、v-model1.1 基本用法1.2 值绑定1.3 修饰符1.3.1 .lazy1.3.2 .number1.3.3 .trim 二、总结2.1 案例2.2 效果 一、v-model 1.1 基本用法 功能&#xff1a; v-model指令在表单 <input>、<textarea> 及 <select> 元素上创建双向数据绑定。它会根据控件…

【人工智能前沿弄潮】——生成式AI系列:扩散模型及稳定扩散模型

VAE、GAN的出现&#xff0c;使得生成式AI越发火热&#xff0c;如今扩散模型的出现与兴起&#xff0c;更是将AIGC推到了人工智能风口&#xff0c;被视作如今人工智能生成艺术领域取得突破的主要因素。相较于VAE和GAN,扩散模型生成的图片质量更好。随着transformer架构的出现和pr…

Uniapp当中使用腾讯位置路线规划插件保姆教学

首先我们在使用腾讯地图插件之前我们需要先做几点准备 1&#xff1a;我们需要在腾讯地图位置服务当中注册账号以及在控制台当中创建应用和创建key 这里在创建应用当中应用类型一定要选出行类型&#xff0c;否则后期可能会出现问题。 我们创建完应用之后&#xff0c;点击创建…

elementUi表单恢复至初始状态并不触发表单验证

elementUi表单恢复至初始状态并不触发表单验证 1.场景再现2.解决方法 1.场景再现 左侧是树形列表&#xff0c;右侧是显示节点的详情&#xff0c;点击按钮应该就是新增一个规则的意思&#xff0c;表单内容是没有改变的&#xff0c;所以就把需要把表单恢复至初始状态并不触发表单…

线性代数(三) 线性方程组向量空间

前言 如何利用行列式&#xff0c;矩阵求解线性方程组。 线性方程组的相关概念 用矩阵方程表示 齐次线性方程组&#xff1a;Ax0&#xff1b;非齐次线性方程组&#xff1a;Axb. 可以理解 齐次线性方程组 是特殊的 非齐次线性方程组 如何判断线性方程组的解 其中R(A)表示矩阵A的…

Hugging News #0414: Attention 在多模态情景中的应用、Unity API 以及 Gradio 主题构建器

社区动向 Attention 在视觉领域的应用 注意力机制改变了许多学科的深度学习研究&#xff0c;从 NLP 开始扩展到视觉、语音等。注意力机制的使用在深度学习研究中变得越来越流行&#xff0c;理解和解释注意力机制的内部工作是至关重要的。 我们发布了一个教程&#xff0c;介绍…

面试官:前面我们聊了主从和哨兵,那今天来聊一聊集群吧

目录 秃顶面试官&#xff1a;简单介绍下什么是Redis Cluster呢? 秃顶面试官&#xff1a;那集群的缺点有哪些呢&#xff1f; 秃顶面试官&#xff1a;说说如何搭建集群呢&#xff1f; 秃顶面试官&#xff1a;集群内部是如何通信的呢&#xff1f; 秃顶面试官&#xff1a;线上…

three.js上传模型文件并加载显示

效果大概这样&#xff0c;这个宝箱模型是直接初始化就显示的&#xff0c;人物模型是自己本地添加上去的&#xff0c;代码如下。 <template><div class"container" ref"container"><el-row><el-col :span"24"><div c…

固态硬盘恢复数据,5步搞定!

“不知咋回事&#xff0c;我的固态硬盘突然就有问题了&#xff0c;很多重要的文件也一起丢失了。我正在很努力的解决这个问题&#xff0c;但可惜我对电脑的使用了解比较少&#xff0c;有没有电脑高手可以帮帮我呀&#xff1f; 固态硬盘&#xff0c;通常被称为SSD&#xff0c;它…

stable-diffusion 模型效果+prompt

摘自个人印象笔记&#xff0c;图不完整可查看原笔记&#xff1a;https://app.yinxiang.com/fx/55cda0c6-2af5-4d66-bd86-85da79c5574ePrompt运用规则及技巧 &#xff1a; 1. https://publicprompts.art/&#xff08;最适用于OpenArt 线上模型 https://openart.ai/&#xff09;…

近地面无人机植被定量遥感与生理参数反演技术

遥感&#xff08;RS-Remote Sensing&#xff09;——不接触物体本身&#xff0c;用传感器收集目标物的电磁波信息&#xff0c;经处理、分析后&#xff0c;识别目标物&#xff0c;揭示其几何、物理性质和相互关系及其变化规律的现代科学技术。 换言之&#xff0c;即是“遥远的感…

领克08,开创「芯」时代

中国汽车产业&#xff08;自主品牌&#xff09;的持续向上&#xff0c;正带动本土智能化核心供应链&#xff0c;尤其是关键算力芯片&#xff08;SoC&#xff09;的本土创新。 高工智能汽车研究院监测数据显示&#xff0c;今年1-6月中国市场&#xff08;不含进出口&#xff09;自…

Unity游戏源码分享-精品即时战略游戏_官网60美刀素材

Unity游戏源码分享-精品即时战略游戏_官网60美刀素材 下载地址&#xff1a;https://download.csdn.net/download/Highning0007/88204017

visual studio 2022安装本地的nuget包

工具->选项->Nuget包管理器->程序包源 然后右击解决方案->管理解决方案的Nuget包