洛谷 UVA1309 Sudoku 黑 题解
UVA1309 Sudoku 黑 题解
前言
这道题可以用搜索剪枝做,但码量大,效率也稍微低一些,所以在此我们聊一聊用精准覆盖(\(DLX\))该怎么做。
思路
和网络流一样,我们先建模,才能跑模板。
一般而言,精准覆盖问题中行是进行选择的,列是进行约束的。 可以发现该题中有以下限制条件:
- 每个格子恰好一个字母。
- 每行中 \(0 \sim 15\) 的所有数字出现且仅出现一次。
- 每列中 \(0 \sim 15\) 的所有数字出现且仅出现一次。
- 每个 \(16\) 宫格中 \(0 \sim 15\) 的所有数字出现且仅出现一次。
尝试将以上四个条件用“列”来满足。
- 用 \(1 \sim 16^2\) 列来满足条件一(将每个格子建成一列)。
- 用 \(16 ^ 2 + 1 \sim 16^2 + 16^2\) 列来满足条件二(将每行建成一列)。
- 用 \(16 ^ 2 \times 2 + 1 \sim 16^2 \times 2 + 16^2\) 列来满足条件三(将每列建成一列)。
- 用 \(16 ^ 2 \times 3 + 1 \sim 16^2 \times 3 + 16^2\) 列来满足条件四(将每个 \(16\) 宫格建成一列)。
同时令“行”代表每个格子选哪个数,由于共有 \(16 \times 16\) 个格子,且最坏情况下每个格子能填 \(16\) 种数字,所以总共有 \(16 ^3\) 行,最多总共有 \(16^3 \times 4 = 16384\) 个 \(1\)。
如果你想更严谨些,可以考虑原问题的方案和我们所抽象出来的精确覆盖问题的方案是否一一对应,这个问题很简单,就不赘述了。
Code
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 20000;
int m = 16 * 16 * 4;
int u[N], d[N], l[N], r[N];
int s[N], col[N], row[N], idx;
int ans[N], top;
struct Op
{
int x, y;
char z;
} op[N];
char g[20][20];
void init()
{
for (int i = 0; i <= m; i ++ )
{
l[i] = i - 1;
r[i] = i + 1;
s[i] = 0;
d[i] = u[i] = i;
}
l[0] = m;
r[m] = 0;
idx = m + 1;
return;
}
void add(int &hh, int &tt, int x, int y)
{
row[idx] = x;
col[idx] = y;
s[y] ++ ;
u[idx] = y;
d[idx] = d[y];
u[d[y]] = idx;
d[y] = idx;
r[hh] = l[tt] = idx;
r[idx] = tt;
l[idx] = hh;
tt = idx ++ ;
return;
}
void remove(int p)
{
r[l[p]] = r[p];
l[r[p]] = l[p];
for (int i = d[p]; i != p; i = d[i])
for (int j = r[i]; j != i; j = r[j])
{
s[col[j]] -- ;
u[d[j]] = u[j];
d[u[j]] = d[j];
}
return;
}
void resume(int p)
{
for (int i = u[p]; i != p; i = u[i])
for (int j = l[i]; j != i; j = l[j])
{
u[d[j]] = j;
d[u[j]] = j;
s[col[j]] ++ ;
}
r[l[p]] = p;
l[r[p]] = p;
return;
}
bool dfs()
{
if (!r[0]) return true;
int p = r[0];
for (int i = r[0]; i; i = r[i])
if (s[i] < s[p]) p = i;
remove(p);
for (int i = d[p]; i != p; i = d[i])
{
ans[ ++ top] = row[i];
for (int j = r[i]; j != i; j = r[j])
remove(col[j]);
if (dfs()) return true;
for (int j = l[i]; j != i; j = l[j])
resume(col[j]);
top -- ;
}
resume(p);
return false;
}
int main()
{
cin >> g[0];
while (1)
{
for (int i = 1; i < 16; i ++ )
cin >> g[i];
init();
for (int i = 0, n = 1; i < 16; i ++ )
for (int j = 0; j < 16; j ++ )
{
int a = 0, b = 15;
if (g[i][j] != '-') a = b = g[i][j] - 'A';
for (int k = a; k <= b; k ++ , n ++ )
{
int hh = idx, tt = idx;
op[n] = {i, j, k + 'A'};
add(hh, tt, n, i * 16 + j + 1); //每个格恰好1个数
add(hh, tt, n, 256 + i * 16 + k + 1); //每行0~15恰一个
add(hh, tt, n, 256 * 2 + j * 16 + k + 1); //每列0~15恰一个
add(hh, tt, n, 256 * 3 + (i / 4 * 4 + j / 4) * 16 + k + 1); //每个16宫格0~15各一次
}
}
dfs();
for (int i = 1; i <= top; i ++ )
{
Op t = op[ans[i]];
g[t.x][t.y] = t.z;
}
for (int i = 0; i < 16; i ++ ) puts(g[i]);
if (cin >> g[0]) puts("");
else break;
}
return 0;
}
后语
最后不能有多余空行!!!
还有就是,为什么此题的内存限制是 \(0B\) 啊

舞蹈链(DLX),也叫精准覆盖问题,也叫 Dancing Links,挺难理解的(然而代码并不长,所以我们可以背),这是应用,如何存储是个难题。
浙公网安备 33010602011771号