双向循环链表、dancing links

目录

双向循环链表

力扣 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;//每行第一个节点的idvector<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到mDancingLink(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);//记录选中idrows.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;//每行第一个节点的idvector<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;//每行第一个节点的idvector<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到mbool 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;//每行第一个节点的idvector<int>up, down, lef, rig;//每个节点上下左右的节点id
};

V4耗时219毫秒

X算法(V5非递归版)

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

class DancingLink
{
public:vector<int>sc, rows;//覆盖选中的行,值的范围是从1到mint 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;//记录选中idrows[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;//每行第一个节点的idvector<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到mDancingLink(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;//记录选中idrows[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;//每行第一个节点的idvector<int>up, down, lef, rig;//每个节点上下左右的节点idvector<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;//每行第一个节点的idstd::vector<int>up, down, lef, rig;//每个节点上下左右的节点idstd::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;//每行第一个节点的idstd::vector<int>up, down, lef, rig;//每个节点上下左右的节点idstd::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;//记录选中idrows[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;//每行第一个节点的idvector<int>up, down, lef, rig;//每个节点上下左右的节点idvector<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>0vector<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>0vector<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.rhkb.cn/news/86334.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

手机app测试

一、安装、卸载、更新、运行 1.安装、卸载 应用是否可以正常安装&#xff08;命令行安装&#xff1b;apk&#xff0f;ipa安装包安装&#xff09;&#xff08;有网&#xff0c;无网是否都正常&#xff09;卸载过程中出现死机&#xff0c;断电&#xff0c;重启等意外的情况&…

关于ArrayList的十三连问

文章目录 一、底层存储结构是什么二、初始容量三、构造方法四、扩容原理五、读写速度比较六、克隆为深克隆还是浅克隆七、多线程环境下是否安全八、增强遍历时添加或删除元素会发生什么事情九、为什么数组被transient修饰十、通过subList()获得的集合能否转为ArrayList十一、使…

如何在 .NET Core WebApi 中处理 MultipartFormDataContent 中的文件

问题描述# 上图示例展示了用户通过 IOS 客户端发送请求时&#xff0c;对应后端接口接收到的 Request 内容。从请求内容的整体结果&#xff0c;我们可以看出这是一个 multipart/form-data 的数据格式&#xff0c;由于这种数据是由多个 multipart section 组成&#xff0c;所以我…

在next中使用antd表格,表格使用render函数报错

Error: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server". {title: "姓名", dataIndex: "name", key: ..., render: function} 错误描述&#xff1a;使用antd的tabl…

【FAQ】安防监控视频EasyCVR平台分发的FLV视频流在VLC中无法播放

众所周知&#xff0c;TSINGSEE青犀视频汇聚平台EasyCVR可支持多协议方式接入&#xff0c;包括主流标准协议国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。在视频流的处理与分发上&#xff0c;视频监控…

资深媒体人宋繁银加入《数据猿》任总编辑,全面负责公司整体内容工作

大数据产业创新服务媒体 ——聚焦数据 改变商业 2023年7月北京&#xff0c;《数据猿》宣布正式任命宋繁银为总编辑&#xff0c;全面负责公司整体内容工作。此次重要的人事任命标志着《数据猿》的发展迈上了一个新的台阶&#xff0c;对于《数据猿》团队而言&#xff0c;不仅是一…

LISA:通过大语言模型进行推理分割

论文&#xff1a;https://arxiv.org/pdf/2308.00692 代码&#xff1a;GitHub - dvlab-research/LISA 摘要 尽管感知系统近年来取得了显著的进步&#xff0c;但在执行视觉识别任务之前&#xff0c;它们仍然依赖于明确的人类指令来识别目标物体或类别。这样的系统缺乏主动推理…

谱包络之pysptk和pyworld

谱包络之pysptk和pyworld 谱包络可以直接用于语音的合成&#xff0c;常用的两个计算谱包络的库pysptk和pyword。 先看看代码&#xff1a; 一段语音x&#xff0c;采样率16000Hz pysptk import pysptkframe_length 1024 hop_length 80 order 25 alpha 0.41 frames libro…

个保新标 | 《信息安全技术 敏感个人信息处理安全要求》(征求意见稿)发布

8 月 9 日&#xff0c;全国信息安全标准化技术委员会公开发布关于国家标准《信息安全技术 敏感个人信息处理安全要求》&#xff08;征求意见稿&#xff09;&#xff08;以下简称《标准》&#xff09;的通知&#xff0c;面向社会广泛征求意见。 《标准》的制定背景是为支撑《个人…

k8s pod启动报错: no route to host

k8s pod kuboard启动报错 查看pod命令 kubectl get pods -A kubectl get pods --all-namespaces查看报错pod日志 命令&#xff1a; kubectl logs -f -n namespace nametime"2023-08-09T13:40:3608:00" levelerror msg"不能获取 AgentEndpointsGet \"http:/…

【论文阅读】基于深度学习的时序预测——FEDformer

系列文章链接 论文一&#xff1a;2020 Informer&#xff1a;长时序数据预测 论文二&#xff1a;2021 Autoformer&#xff1a;长序列数据预测 论文三&#xff1a;2022 FEDformer&#xff1a;长序列数据预测 论文四&#xff1a;2022 Non-Stationary Transformers&#xff1a;非平…

如何实现Excel中多级数据联动

摘要&#xff1a;本文由葡萄城技术团队于CSDN原创并首发。转载请注明出处&#xff1a;葡萄城官网&#xff0c;葡萄城为开发者提供专业的开发工具、解决方案和服务&#xff0c;赋能开发者。 前言 在类Excel表格应用中&#xff0c;常用的需求场景是根据单元格之间的数据联动&…

计算机视觉的应用10-图片中的表格结构识别与提取实战

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下计算机视觉的应用10-图片中的表格结构识别与提取实战&#xff0c;表格结构识别在信息处理领域中具有广泛应用&#xff0c;但由于表格的多样性和复杂性&#xff0c;以及难以准确解析的布局和格式&#xff0c;传统的方…

读《Flask Web开发实战》(狼书)笔记 | 第1、2章

前言 2023-8-11 以前对网站开发萌生了想法&#xff0c;又有些急于求成&#xff0c;在B站照着视频敲了一个基于flask的博客系统。但对于程序的代码难免有些囫囵吞枣&#xff0c;存在许多模糊或不太理解的地方&#xff0c;只会照葫芦画瓢。 而当自己想开发一个什么网站的时&…

ad+硬件每日学习十个知识点(26)23.8.6 (DCDC的降压电路、升压电路、降压-升压电路,同步整流,选型考虑同步、隔离)

文章目录 1.DCDC的降压原理2.DCDC的升压原理3.DCDC的升压和降压原理4.什么是肖特基二极管造成的死区电压&#xff1f;5.MOS管有死区电压么&#xff1f;6.DCDC的同步整流&#xff08;用MOS管取代整流二极管&#xff0c;避免死区电压的影响&#xff09;7.DCDC选型——同步与非同步…

分清性能测试,负载测试,压力测试这三个的区别

做测试一年多来&#xff0c;虽然平时的工作都能很好的完成&#xff0c;但最近突然发现自己在关于测试的整体知识体系上面的了解很是欠缺&#xff0c;所以&#xff0c;在工作之余也做了一些测试方面的知识的补充。不足之处&#xff0c;还请大家多多交流&#xff0c;互相学习。 …

AI:02-基于深度学习的动物图像检索算法的研究

文章目录 一、算法原理二、代码实现三、实验结果四、总结深度学习在计算机视觉领域中的应用越来越广泛,其中动物图像检索算法是一个重要的应用场景。本文将介绍一种基于深度学习的动物图像检索算法,并提供相应的代码实现。 一、算法原理 本算法采用卷积神经网络(Convolutio…

Selenium 根据元素文本内容定位

使用xpath定位元素时&#xff0c;有时候担心元素位置会变&#xff0c;可以考虑使用文本内容来定位的方式。 例如图中的【股市】按钮&#xff0c;只有按钮文本没变&#xff0c;即使位置变化也可以定位到该元素。 xpath内容样例&#xff1a; # 文本内容完全匹配 //button[text(…

勘探开发人工智能技术:机器学习(6)

0 提纲 7.1 循环神经网络RNN 7.2 LSTM 7.3 Transformer 7.4 U-Net 1 循环神经网络RNN 把上一时刻的输出作为下一时刻的输入之一. 1.1 全连接神经网络的缺点 现在的任务是要利用如下语料来给apple打标签&#xff1a; 第一句话&#xff1a;I like eating apple!(我喜欢吃苹…

Mac M1 安装Oracle Java 与 IEDA

文章目录 1 官网下载2 安装IDEA参考 1 官网下载 https://www.oracle.com/ 使用finder中的拖拽进行安装即可 2 安装IDEA https://www.jetbrains.com/zh-cn/idea/download/?sectionmac 同样的&#xff0c;下载完后拖拽安装即可 参考 Mac M1 安装Java 开发环境 https://blog.…