题意:数独,每行,每列,每宫内均要出现且只出现一次‘A’-‘P'这16个字母
输出满足条件的一种情况,否则输出-1
前置知识:DLX:Dancing Links(使用前提:稀疏矩阵)
题型1:精确覆盖问题:n行m列至少选多少行使得每列中恰好有一个1
题型2:重复覆盖问题:n行m列至少选多少行使得每列中至少有一个1
B31【模板】舞蹈链(DLX)精确覆盖问题_哔哩哔哩_bilibili
舞蹈链问题中,如果枚举所有情况是每一行选或者不选(2^n),最后还要检查是否满足精确覆盖(n*m)时间复杂度O(n*m*2^n),所以引入了舞蹈链算法,可以在较低的时间复杂度内解决稀疏矩阵的精确覆盖问题
很多关于精确覆盖的问题都可以转化为DLX,比如:
Treasure Map ZOJ - 3209
题目给定初始矩形(1<=n,m<=30),输入几个小矩形,问如何使用最少的小矩形来完全表示初始矩形,要求不能重复覆盖,这一类题目就可以转化为DLX问题,把所有的小矩形展开变成01序列,每一列01代表矩形的位置,每一行代表每一个小矩形。
回到这道题目可以发现,每个点如果未知的话有十六种情况,如果已知的话就是一种情况,尝试转化成01矩阵,用列代表依赖,行代表每一个选择
首先找到每一列
可以发现:
每一个点有四个依赖:①当前坐标②某一行的A~P③每一列的A~P④每一宫的A~P,
①:当前坐标(i,j)填入一个字母对应256个位置-->256列
②:1~16行每一个位置可以填A~P---------------->256列
③:1~16列每一个位置可以填A~P---------------->256列
④:1~16宫每一个宫可以填A~P------------------->256列
然后一共256*4=1024列,恰好填满每一列才能表示正确的答案
(PS:形象一点说,如果此时1024列都填满了代表什么:①每个位置填满②16行每一行都填满了A~P③16列每一列都填满了A~P④16宫每一宫都填满了A~P)
接下来是每一行:
假设(0,0)这个点未填数字,那么要枚举A~P的16中情况,假设填了'C',代表①②③④对应的位置都要有一个1
①:当前坐标(0,0)-->第1列填1(代表1行1列有数字)
②:在第1行填C----->第256+3列填1(256代表偏移量因为前面①占了256个位置,3代表第1行第3个字母)
③:在第1列填C----->第256*2+3列填1(3代表第1列第3个字母)
④:在第1宫填C----->第256*3+3列填1(3代表第1宫第3个字母)
假设(0,0)这个点填了字母,那么要把这个点的的状态记录下来,记录方法同上,并且不用担心这个点到时候跑DLX的时候不被选到,因为要精确覆盖每一个点的话,这个点一定要选,因为有①条件,代表(0,0)这个点至少要选一个状态,而已有字母的情况下只有一个状态,那么肯定要选这个状态
知道了思路之后,还有一个难点就是如何把每个坐标的每一个情况转化到列上,以及已知所有行的选择情况之后如何把每一行的状态又转化回A~P?
一:把情况转化为列号:
首先遍历每个点(i,j),把A~P由1~16编号(假设为k),遍历每个编号
那么这个点对应的行号就是i*16*16+j*16+k
①情况的列号:i*16+j+1
②情况的列号256+i*16+k
③情况的列号256*2+j*16+k
④情况的列号256*3+(i/4*4+j/4)*16+k;
二:把所有选择的行转化为坐标和答案(A~P):
假设此时选择的行号为x
横坐标(x-1)/16/16
纵坐标(x-1)/16%16
当前位置的答案(x-1)%16+1;
于是本题得解:
代码实现:
#include <map>
#include <queue>
#include <deque>
#include <cmath>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_map>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define pp pop_back()
#define int long long
#define laile cout<<"laile"<<endl
#define lowbit(x) ((x)&(-x))
#define double long double
#define sf(x) scanf("%lld",&x)
#define sff(x,y) scanf("%lld %lld",&x,&y)
#define _for(i,n) for(int i=0;i<(n);++i)
#define _rep(i,a,b) for(int i=(a);i<=(b);++i)
#define all(x) (x).begin(), (x).end()
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
typedef unsigned long long ULL;
typedef pair<int,int>PII;
const int N=5e6,INF=4e18;
int n,m;
int r[N],l[N],d[N],u[N];
int row[N],col[N],idx;
int h[N];//每行头节点
int s[N];//列元素个数
int res[N];
char ans[20][20];
string str[20];
void init()
{
for(int i=0;i<=m;i++)
{
u[i]=i,d[i]=i;
l[i]=i-1,r[i]=i+1;
}
l[0]=m,r[m]=0;
idx=m;
return;
}
void remove(int y)
{
r[l[y]]=r[y],l[r[y]]=l[y];
for(int i=d[y];i!=y;i=d[i])
for(int j=r[i];j!=i;j=r[j])
u[d[j]]=u[j],d[u[j]]=d[j],s[col[j]]--;
return ;
}
void remake(int y)
{
r[l[y]]=y,l[r[y]]=y;
for(int i=u[y];i!=y;i=u[i])
for(int j=r[i];j!=i;j=r[j])
u[d[j]]=j,d[u[j]]=j,s[col[j]]++;
return ;
}
bool space;
bool dance(int dep)
{
if(!r[0])
{
_for(i,dep)
{
// int now=i*16*16+j*16+k;(i:[0,15],j:[0,15],k:[1,16])
int x=(res[i]-1)/16/16;//PS:不整除的话不能直接把除号分给被除数每个元素
int y=(res[i]-1)/16%16;
int k=(res[i]-1)%16+1;
ans[x][y]=k;
}
_rep(i,0,15)
{
_rep(j,0,15)
{
cout<<(char)(ans[i][j]+'A'-1);
}
cout<<endl;
}
return true;
}
int y=r[0];
for(int i=r[0];i;i=r[i])
if(s[i]<s[y])y=i;
remove(y);
for(int i=d[y];i!=y;i=d[i])
{
res[dep]=row[i];
for(int j=r[i];j!=i;j=r[j])remove(col[j]);
if(dance(dep+1))return true;
for(int j=l[i];j!=i;j=l[j])remake(col[j]);
}
remake(y);
return false;
}
void add(int x,int y)
{
row[++idx]=x,col[idx]=y;
s[y]++;
u[idx]=u[y];
d[u[y]]=idx;
d[idx]=y;
u[y]=idx;
if(!h[x])h[x]=r[idx]=l[idx]=idx;
else
{
l[idx]=l[h[x]];
r[l[h[x]]]=idx;
r[idx]=h[x];
l[h[x]]=idx;
}
}
void solve()
{
while(cin>>str[0])
{
if(space)cout<<endl;
space=true;
n=4096,m=1024;
for(int i=0;i<=n;i++)h[i]=0;
for(int j=0;j<=m;j++)s[j]=0;
idx=0;
init();
_for(i,16)
{
if(i)cin>>str[i];
_for(j,16)
{
char c;
int a;
c=str[i][j];
if(c=='-')a=0;
else a=c-'A'+1;
_rep(k,1,16)
{
if(!a||a==k)
{
int x=i*16*16+j*16+k;
add(x,i*16+j+1);
add(x,256+i*16+k);
add(x,256*2+j*16+k);
add(x,256*3+(i/4*4+j/4)*16+k);
}
}
}
}
dance(0);
}
return;
}
signed main()
{
int T=1;
// cin>>T;
while(T--)
solve();
return 0;
}